task-8: callJava AbortController 透传契约测试(发送前/进行中中止、signal 透传、多调用独立)
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { http } from '../src/shared/api/http.ts'
|
||||
import { callJava } from '../src/shared/api/call.ts'
|
||||
|
||||
function mockRequest(t: Parameters<typeof test>[1] extends (t: infer T) => unknown ? T : never, impl: (config: { signal?: AbortSignal }) => Promise<unknown>) {
|
||||
t.mock.method(http, 'request', impl as never)
|
||||
}
|
||||
|
||||
function abortedError() {
|
||||
const error = new Error('canceled')
|
||||
error.name = 'CanceledError'
|
||||
return error
|
||||
}
|
||||
|
||||
test('test_signal_abort_before_send', async (t) => {
|
||||
const controller = new AbortController()
|
||||
controller.abort()
|
||||
mockRequest(t, (config) => {
|
||||
if (config.signal?.aborted) {
|
||||
throw abortedError()
|
||||
}
|
||||
return Promise.resolve({ data: { success: true, message: 'ok', data: 1 } })
|
||||
})
|
||||
await assert.rejects(
|
||||
callJava({ url: '/newApi/api/x', method: 'GET', signal: controller.signal }),
|
||||
(error: Error) => /canceled|Abort/i.test(error.message),
|
||||
'发送前已中止应立即拒绝',
|
||||
)
|
||||
})
|
||||
|
||||
test('test_signal_abort_mid_request', async (t) => {
|
||||
const controller = new AbortController()
|
||||
mockRequest(t, (config) =>
|
||||
new Promise((_resolve, reject) => {
|
||||
config.signal?.addEventListener('abort', () => reject(abortedError()))
|
||||
}),
|
||||
)
|
||||
const promise = callJava({ url: '/newApi/api/x', method: 'GET', signal: controller.signal })
|
||||
setTimeout(() => controller.abort(), 5)
|
||||
await assert.rejects(promise, /canceled/, '进行中中止应拒绝')
|
||||
})
|
||||
|
||||
test('test_signal_passthrough', async (t) => {
|
||||
const controller = new AbortController()
|
||||
let captured: unknown
|
||||
mockRequest(t, (config) => {
|
||||
captured = config.signal
|
||||
return Promise.resolve({ data: { success: true, message: 'ok', data: 1 } })
|
||||
})
|
||||
await callJava({ url: '/newApi/api/x', method: 'GET', signal: controller.signal })
|
||||
assert.equal(captured, controller.signal, 'signal 应透传到 axios 配置')
|
||||
})
|
||||
|
||||
test('test_signal_abort_error_message', async (t) => {
|
||||
const controller = new AbortController()
|
||||
controller.abort()
|
||||
mockRequest(t, (config) => {
|
||||
if (config.signal?.aborted) {
|
||||
throw abortedError()
|
||||
}
|
||||
return Promise.resolve({ data: { success: true, message: 'ok', data: 1 } })
|
||||
})
|
||||
await assert.rejects(
|
||||
callJava({ url: '/newApi/api/x', method: 'GET', signal: controller.signal }),
|
||||
(error: Error) => /canceled|Abort/i.test(error.message) || error.name === 'CanceledError',
|
||||
'错误消息应含 AbortError 特征',
|
||||
)
|
||||
})
|
||||
|
||||
test('test_signal_not_provided', async (t) => {
|
||||
mockRequest(t, () => Promise.resolve({ data: { success: true, message: 'ok', data: 'ok' } }))
|
||||
const result = await callJava<string>({ url: '/newApi/api/x', method: 'GET' })
|
||||
assert.equal(result, 'ok')
|
||||
})
|
||||
|
||||
test('test_abort_after_resolve', async (t) => {
|
||||
const controller = new AbortController()
|
||||
mockRequest(t, () => Promise.resolve({ data: { success: true, message: 'ok', data: 1 } }))
|
||||
const result = await callJava({ url: '/newApi/api/x', method: 'GET', signal: controller.signal })
|
||||
controller.abort()
|
||||
assert.equal(result, 1, '完成后 abort 不应有副作用')
|
||||
})
|
||||
|
||||
test('test_abort_rejects_promise', async (t) => {
|
||||
const controller = new AbortController()
|
||||
mockRequest(t, (config) =>
|
||||
new Promise((_resolve, reject) => {
|
||||
config.signal?.addEventListener('abort', () => reject(abortedError()))
|
||||
}),
|
||||
)
|
||||
const promise = callJava({ url: '/newApi/api/x', method: 'GET', signal: controller.signal })
|
||||
controller.abort()
|
||||
await assert.rejects(promise, /canceled/, '中止应拒绝 Promise')
|
||||
})
|
||||
|
||||
test('test_signal_multiple_calls_independent', async (t) => {
|
||||
const first = new AbortController()
|
||||
const second = new AbortController()
|
||||
let calls = 0
|
||||
mockRequest(t, (config) => {
|
||||
calls += 1
|
||||
if (config.signal?.aborted) {
|
||||
throw abortedError()
|
||||
}
|
||||
return Promise.resolve({ data: { success: true, message: 'ok', data: calls } })
|
||||
})
|
||||
second.abort()
|
||||
const [a, b] = await Promise.allSettled([
|
||||
callJava({ url: '/newApi/api/x', method: 'GET', signal: first.signal }),
|
||||
callJava({ url: '/newApi/api/y', method: 'GET', signal: second.signal }),
|
||||
])
|
||||
assert.equal(a.status, 'fulfilled', '未中止的调用应成功')
|
||||
assert.equal(b.status, 'rejected', '已中止的调用应失败')
|
||||
})
|
||||
Reference in New Issue
Block a user