From 980e6e91e07fccaee599e6d3e7807faf69846d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Mon, 31 Aug 2026 19:21:56 +0800 Subject: [PATCH] =?UTF-8?q?task-8:=20callJava=20AbortController=20?= =?UTF-8?q?=E9=80=8F=E4=BC=A0=E5=A5=91=E7=BA=A6=E6=B5=8B=E8=AF=95=EF=BC=88?= =?UTF-8?q?=E5=8F=91=E9=80=81=E5=89=8D/=E8=BF=9B=E8=A1=8C=E4=B8=AD?= =?UTF-8?q?=E4=B8=AD=E6=AD=A2=E3=80=81signal=20=E9=80=8F=E4=BC=A0=E3=80=81?= =?UTF-8?q?=E5=A4=9A=E8=B0=83=E7=94=A8=E7=8B=AC=E7=AB=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend-vue/tests/call-abort.test.ts | 115 ++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 frontend-vue/tests/call-abort.test.ts 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', '已中止的调用应失败') +})