mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
Merge pull request #7492 from ouchihao/codex/bug-monitor-detail-race
fix(channel-monitor): ignore stale detail requests
This commit is contained in:
@@ -87,22 +87,27 @@ const { statusLabel, statusBadgeClass, formatLatency, formatPercent, formatMonit
|
||||
|
||||
const detail = ref<UserMonitorDetail | null>(null)
|
||||
const loading = ref(false)
|
||||
let requestVersion = 0
|
||||
|
||||
async function load(id: number) {
|
||||
const version = ++requestVersion
|
||||
detail.value = null
|
||||
loading.value = true
|
||||
try {
|
||||
detail.value = await fetchChannelMonitorDetail(id)
|
||||
const result = await fetchChannelMonitorDetail(id)
|
||||
if (version === requestVersion) detail.value = result
|
||||
} catch (err: unknown) {
|
||||
if (version !== requestVersion) return
|
||||
appStore.showError(extractApiErrorMessage(err, t('channelStatus.detailLoadError')))
|
||||
} finally {
|
||||
loading.value = false
|
||||
if (version === requestVersion) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.show, props.monitorId] as const,
|
||||
([show, id]) => {
|
||||
([show, id], _, onCleanup) => {
|
||||
onCleanup(() => { requestVersion++ })
|
||||
if (!show) {
|
||||
detail.value = null
|
||||
return
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import MonitorDetailDialog from '../MonitorDetailDialog.vue'
|
||||
const mocks = vi.hoisted(() => ({ status: vi.fn(), showError: vi.fn() }))
|
||||
vi.mock('@/api/channelMonitor', () => ({ status: mocks.status }))
|
||||
vi.mock('@/stores/app', () => ({ useAppStore: () => mocks }))
|
||||
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key }) }))
|
||||
enableAutoUnmount(afterEach)
|
||||
beforeEach(() => vi.clearAllMocks())
|
||||
function deferred() {
|
||||
let resolve!: (value: unknown) => void
|
||||
let reject!: (value: unknown) => void
|
||||
const promise = new Promise((res, rej) => { resolve = res; reject = rej })
|
||||
return { promise, resolve, reject }
|
||||
}
|
||||
const detail = (model: string) => ({ models: [{ model, latest_status: 'operational' }] })
|
||||
function open() {
|
||||
return mount(MonitorDetailDialog, { props: { show: true, monitorId: 1, title: 'Monitor' },
|
||||
global: { stubs: { BaseDialog: { props: ['show'], template: '<div v-if="show"><slot /></div>' } } } })
|
||||
}
|
||||
describe('monitor detail request ownership', () => {
|
||||
it('keeps the new monitor response when the old response arrives last', async () => {
|
||||
const old = deferred()
|
||||
mocks.status.mockReturnValueOnce(old.promise).mockResolvedValueOnce(detail('new-model'))
|
||||
const w = open(); await w.setProps({ monitorId: 2 }); await flushPromises()
|
||||
old.resolve(detail('old-model')); await flushPromises()
|
||||
expect(w.text()).toContain('new-model'); expect(w.text()).not.toContain('old-model')
|
||||
})
|
||||
it('ignores a previous failure while the current monitor is loading', async () => {
|
||||
const old = deferred(); const current = deferred()
|
||||
mocks.status.mockReturnValueOnce(old.promise).mockReturnValueOnce(current.promise)
|
||||
const w = open(); await w.setProps({ show: false }); await w.setProps({ show: true })
|
||||
old.reject(new Error('old failure')); await flushPromises()
|
||||
expect(mocks.showError).not.toHaveBeenCalled(); expect(w.text()).toContain('common.loading')
|
||||
current.resolve(detail('current')); await flushPromises(); expect(w.text()).toContain('current')
|
||||
})
|
||||
it('reports a failure for the current monitor', async () => {
|
||||
mocks.status.mockRejectedValueOnce(new Error('current failure'))
|
||||
const w = open(); await flushPromises()
|
||||
expect(mocks.showError).toHaveBeenCalledWith('current failure')
|
||||
expect(w.text()).toContain('channelStatus.detailLoadError')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user