fix: complete October 22 workflow feedback and request failure recovery

This commit is contained in:
2026-10-22 23:06:06 +08:00
parent 161128ff9e
commit 54e49e2426
28 changed files with 530 additions and 413 deletions
+43
View File
@@ -274,6 +274,49 @@ test('H5 blob photo uses authenticated multipart fetch instead of uni.uploadFile
}
})
test('H5 XMLHttpRequest timeout and abort reject once so the page can retry without an automatic upload replay', async () => {
prepareSession()
const originals = { window: globalThis.window, location: globalThis.location, fetch: globalThis.fetch, XMLHttpRequest: globalThis.XMLHttpRequest }
globalThis.window = {}; globalThis.location = { protocol: 'http:' }
globalThis.fetch = async () => new Response(new Blob(['photo'], { type: 'image/png' }))
const requests = []
globalThis.XMLHttpRequest = class {
constructor() { requests.push(this) }
open() {} setRequestHeader() {}
send() { queueMicrotask(() => requests.length === 1 ? this.ontimeout() : this.onabort()) }
}
try {
await assert.rejects(overseasUpload('/pda/photo', 'blob:photo'), error => error.network && /超时/.test(error.message))
assert.equal(requests.length, 1); assert.equal(requests[0].timeout, 60000)
await assert.rejects(overseasUpload('/pda/photo', 'blob:photo'), error => error.network && /中断/.test(error.message))
assert.equal(requests.length, 2)
} finally {
for (const [key, value] of Object.entries(originals)) { if (value === undefined) delete globalThis[key]; else globalThis[key] = value }
}
})
test('H5 fetch upload aborts a stalled transfer, clears its timer, and permits a later upload', async () => {
prepareSession()
const originals = { window: globalThis.window, location: globalThis.location, fetch: globalThis.fetch, XMLHttpRequest: globalThis.XMLHttpRequest, setTimeout: globalThis.setTimeout, clearTimeout: globalThis.clearTimeout }
let timeoutCallback, posts = 0, cleared = 0
globalThis.window = {}; globalThis.location = { protocol: 'http:' }; delete globalThis.XMLHttpRequest
globalThis.setTimeout = (callback, delay) => { assert.equal(delay, 60000); timeoutCallback = callback; return 'upload-timer' }
globalThis.clearTimeout = handle => { assert.equal(handle, 'upload-timer'); cleared++ }
globalThis.fetch = async (url, options) => {
if (String(url).startsWith('blob:')) return new Response(new Blob(['photo'], { type: 'image/png' }))
if (++posts === 1) return new Promise((_resolve, reject) => { options.signal.addEventListener('abort', () => reject(new Error('aborted'))); queueMicrotask(timeoutCallback) })
return new Response(JSON.stringify({ code: '200', data: { fileId: 'retried-photo' } }), { status: 200 })
}
try {
await assert.rejects(overseasUpload('/pda/photo', 'blob:photo'), error => error.network && /超时/.test(error.message))
assert.equal(posts, 1); assert.equal(cleared, 1)
assert.deepEqual(await overseasUpload('/pda/photo', 'blob:photo'), { fileId: 'retried-photo' })
assert.equal(posts, 2); assert.equal(cleared, 2)
} finally {
for (const [key, value] of Object.entries(originals)) { if (value === undefined) delete globalThis[key]; else globalThis[key] = value }
}
})
test('H5 File photo is appended directly without rereading a temporary blob URL', async () => {
const value = prepareSession()
const originalWindow = globalThis.window