From a24c2068ef64e7fe6a92cc4b9b755d5606482aef Mon Sep 17 00:00:00 2001 From: QiuSW <105186638@qq.com> Date: Mon, 5 Oct 2026 14:50:22 +0800 Subject: [PATCH] fix(#355): extend SYB product list timeout to 60 seconds --- web/src/api/goauto/syb-products.js | 2 +- web/tests/e2e/syb-network-retry.spec.ts | 22 +++++++++ web/tests/unit/syb-products-timeout.test.cjs | 52 ++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 web/tests/unit/syb-products-timeout.test.cjs diff --git a/web/src/api/goauto/syb-products.js b/web/src/api/goauto/syb-products.js index 5c95c81..8831a8e 100644 --- a/web/src/api/goauto/syb-products.js +++ b/web/src/api/goauto/syb-products.js @@ -1,7 +1,7 @@ import request from '@/utils/request' export function listSybProducts(params, options = {}) { - return request({ url: '/api/admin/v1/syb-products', method: 'get', params, ...options }) + return request({ url: '/api/admin/v1/syb-products', method: 'get', params, timeout: 60000, ...options }) } export function getSybProduct(productId) { diff --git a/web/tests/e2e/syb-network-retry.spec.ts b/web/tests/e2e/syb-network-retry.spec.ts index 845dadc..63675b9 100644 --- a/web/tests/e2e/syb-network-retry.spec.ts +++ b/web/tests/e2e/syb-network-retry.spec.ts @@ -2,6 +2,28 @@ import { expect, test } from '@playwright/test' const sybProductRoute = [{ path: '/goauto-order-workbench', component: 'Layout', menuName: 'GoAutoOrderWorkbench', title: '订单工作台', visible: '0', children: [{ path: '/syb-products', component: '/goauto/syb-products/index', menuName: 'GoAutoSybProducts', title: 'SYB 商品', visible: '0' }] }] +test('SYB 商品列表超过 10 秒后仍正常显示,不重复请求', async({ page, context }) => { + await context.addCookies([{ name: 'Admin-Token', value: 'prototype-test-token', domain: 'localhost', path: '/' }]) + let listCalls = 0 + await page.route('**/api/**', async route => { + const url = new URL(route.request().url()) + if (url.pathname.startsWith('/src/api/')) return route.continue() + if (url.pathname.endsWith('/api/v1/getinfo')) return route.fulfill({ json: { code: 200, data: { roles: ['admin'], name: '测试管理员', avatar: '', introduction: '', permissions: [] }}}) + if (url.pathname.endsWith('/api/v1/menurole')) return route.fulfill({ json: { code: 200, data: sybProductRoute }}) + if (url.pathname.endsWith('/api/admin/v1/syb-products')) { + listCalls++ + await new Promise(resolve => setTimeout(resolve, 12000)) + return route.fulfill({ json: { code: 200, data: { items: [{ id: 355, orderCode: 'TEST-355', productTitle: '延迟查询成功的测试商品', quantity: 1, parseStatus: 'success' }], total: 1, page: 1, pageSize: 200 }}}) + } + if (url.pathname.endsWith('/api/admin/v1/purchase-tasks/batch-preview')) return route.fulfill({ json: { code: 200, data: { items: [{ sybProductId: 355, eligible: false, processStage: 'return_pending', processStageLabel: '退货待确认' }] }}}) + return route.fulfill({ json: { code: 200, data: [] }}) + }) + await page.goto('/#/syb-products') + await expect(page.getByText('延迟查询成功的测试商品')).toBeVisible({ timeout: 20000 }) + expect(listCalls).toBe(1) + await expect(page.getByText(/timeout of 10000ms exceeded/)).toHaveCount(0) +}) + test('SYB 商品首次网络失败时静默重试一次并恢复列表', async({ page, context }) => { await context.addCookies([{ name: 'Admin-Token', value: 'prototype-test-token', domain: 'localhost', path: '/' }]) let listCalls = 0 diff --git a/web/tests/unit/syb-products-timeout.test.cjs b/web/tests/unit/syb-products-timeout.test.cjs new file mode 100644 index 0000000..9a44567 --- /dev/null +++ b/web/tests/unit/syb-products-timeout.test.cjs @@ -0,0 +1,52 @@ +const { test } = require('node:test') +const assert = require('node:assert/strict') +const fs = require('node:fs') +const path = require('node:path') + +const source = fs.readFileSync(path.join(__dirname, '../../src/api/goauto/syb-products.js'), 'utf8') +const names = [...source.matchAll(/export function (\w+)/g)].map(match => match[1]) +const load = request => new Function('request', source.replace(/^import .+\n/gm, '').replace(/export function /g, 'function ') + `\nreturn { ${names.join(',')} }`)(request) + +test('search, pagination and page-size changes use the list-only 60-second timeout', () => { + const api = load(config => config) + for (const params of [ + { page: 1, pageSize: 200, shopName: '', processStage: 'return_pending', purchaseType: 'all' }, + { page: 2, pageSize: 200 }, + { page: 1, pageSize: 500 } + ]) { + const config = api.listSybProducts(params) + assert.equal(config.timeout, 60000) + assert.equal(config.params, params) + assert.equal(config.url, '/api/admin/v1/syb-products') + assert.equal(config.method, 'get') + } +}) + +test('existing cancellation and error options are preserved', () => { + const controller = new AbortController() + const config = load(config => config).listSybProducts({}, { signal: controller.signal, suppressNetworkError: true }) + assert.equal(config.timeout, 60000) + assert.equal(config.signal, controller.signal) + assert.equal(config.suppressNetworkError, true) + controller.abort() + assert.equal(config.signal.aborted, true) +}) + +test('other SYB API functions do not acquire the list timeout; global timeout stays 10 seconds', () => { + const api = load(config => config) + for (const name of names.filter(name => name !== 'listSybProducts')) { + assert.equal(api[name](1, {}).timeout, undefined, name) + } + const globalRequest = fs.readFileSync(path.join(__dirname, '../../src/utils/request.js'), 'utf8') + assert.match(globalRequest, /timeout:\s*10000\b/) +}) + +test('timeout and cancellation failures propagate without new retries', async () => { + for (const code of ['ECONNABORTED', 'ERR_CANCELED']) { + let calls = 0 + const failure = Object.assign(new Error(code), { code }) + const api = load(() => { calls++; return Promise.reject(failure) }) + await assert.rejects(api.listSybProducts({}), error => error === failure) + assert.equal(calls, 1) + } +})