diff --git a/frontend-vue/tests/call-abort.test.ts b/frontend-vue/tests/call-abort.test.ts new file mode 100644 index 00000000..a0983b9d --- /dev/null +++ b/frontend-vue/tests/call-abort.test.ts @@ -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[1] extends (t: infer T) => unknown ? T : never, impl: (config: { signal?: AbortSignal }) => Promise) { + 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({ 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', '已中止的调用应失败') +})