Merge pull request #7183 from ouchihao/codex/bug-proxy-test-dedup

fix(proxies): reuse in-flight test guards during batch testing
This commit is contained in:
Wesley Liddick
2026-09-17 16:39:34 +08:00
committed by GitHub
2 changed files with 57 additions and 14 deletions
@@ -274,20 +274,7 @@ const handleBatchTest = async () => {
batchTesting.value = true
// Test all proxies in parallel
const testPromises = props.proxies.map(async (proxy) => {
testingProxyIds.add(proxy.id)
try {
const result = await adminAPI.proxies.testProxy(proxy.id)
testResults[proxy.id] = result
} catch (error: any) {
testResults[proxy.id] = {
success: false,
message: error.response?.data?.detail || 'Test failed'
}
} finally {
testingProxyIds.delete(proxy.id)
}
})
const testPromises = props.proxies.map(handleTestProxy)
await Promise.all(testPromises)
batchTesting.value = false
@@ -0,0 +1,56 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
import ProxySelector from '../ProxySelector.vue'
import type { Proxy } from '@/types'
const testProxy = vi.hoisted(() => vi.fn())
vi.mock('@/api/admin', () => ({ adminAPI: { proxies: { testProxy } } }))
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key }) }))
enableAutoUnmount(afterEach)
beforeEach(() => { vi.clearAllMocks() })
async function openSelector() {
const wrapper = mount(ProxySelector, {
props: { modelValue: null, proxies: [1, 2].map(id => ({
id, name: `Proxy ${id}`, host: 'localhost', port: 8080, protocol: 'http'
} as Proxy)) },
global: { stubs: { Icon: true } }
})
await wrapper.get('.select-trigger').trigger('click')
return wrapper
}
describe('proxy connection tests', () => {
it('does not restart an individual test when a batch is started', async () => {
let finish!: (result: object) => void
testProxy.mockImplementation((id: number) => id === 1
? new Promise(resolve => { finish = resolve })
: Promise.resolve({ success: true, country: 'GB' }))
const wrapper = await openSelector()
await wrapper.findAll('.test-btn')[0].trigger('click')
await wrapper.get('.batch-test-btn').trigger('click')
await flushPromises()
expect(testProxy.mock.calls.map(([id]) => id)).toEqual([1, 2])
expect(wrapper.findAll('.test-btn')[0].attributes('disabled')).toBeDefined()
finish({ success: true, country: 'US' })
await flushPromises()
expect(wrapper.text()).toContain('US')
expect(wrapper.findAll('.test-btn')[0].attributes('disabled')).toBeUndefined()
})
it('shows per-proxy outcomes and allows another batch after a failure', async () => {
testProxy.mockImplementation((id: number) => id === 1
? Promise.reject(new Error('offline'))
: Promise.resolve({ success: true, country: 'GB' }))
const wrapper = await openSelector()
await wrapper.get('.batch-test-btn').trigger('click')
await flushPromises()
expect(testProxy).toHaveBeenCalledTimes(2)
expect(wrapper.text()).toContain('admin.proxies.testFailed')
expect(wrapper.text()).toContain('GB')
expect(wrapper.get('.batch-test-btn').attributes('disabled')).toBeUndefined()
await wrapper.get('.batch-test-btn').trigger('click')
await flushPromises()
expect(testProxy).toHaveBeenCalledTimes(4)
})
})