Merge pull request #6764 from ouchihao/codex/issue-1499-select-failed-token-refresh

fix(accounts): keep failed token refresh accounts selected
This commit is contained in:
Wesley Liddick
2026-09-08 20:11:08 +08:00
committed by GitHub
2 changed files with 47 additions and 5 deletions
+4 -1
View File
@@ -1891,10 +1891,13 @@ const handleBulkResetStatus = async () => {
}
const handleBulkRefreshToken = async () => {
if (!confirm(t('common.confirm'))) return
const accountIds = [...selIds.value]
try {
const result = await adminAPI.accounts.batchRefresh(selIds.value)
const result = await adminAPI.accounts.batchRefresh(accountIds)
if (result.failed > 0) {
appStore.showError(t('admin.accounts.bulkActions.partialSuccess', { success: result.success, failed: result.failed }))
const failedIds = result.errors?.map(error => error.account_id) ?? []
setSelectedIds(failedIds.length > 0 ? failedIds : accountIds)
} else {
appStore.showSuccess(t('admin.accounts.bulkActions.refreshTokenSuccess', { count: result.success }))
clearSelection()
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { flushPromises, mount } from '@vue/test-utils'
import AccountsView from '../AccountsView.vue'
@@ -6,6 +6,7 @@ import AccountsView from '../AccountsView.vue'
const {
listAccounts,
listWithEtag,
batchRefresh,
getBatchTodayStats,
getUpstreamBillingProbeSettings,
getAllProxies,
@@ -14,6 +15,7 @@ const {
} = vi.hoisted(() => ({
listAccounts: vi.fn(),
listWithEtag: vi.fn(),
batchRefresh: vi.fn(),
getBatchTodayStats: vi.fn(),
getUpstreamBillingProbeSettings: vi.fn(),
getAllProxies: vi.fn(),
@@ -30,7 +32,7 @@ vi.mock('@/api/admin', () => ({
getUpstreamBillingProbeSettings,
batchDelete: vi.fn(),
batchClearError: vi.fn(),
batchRefresh: vi.fn(),
batchRefresh,
bulkUpdate: vi.fn()
},
proxies: {
@@ -79,7 +81,7 @@ const makeAccounts = (count: number) => Array.from({ length: count }, (_, index)
const AccountBulkActionsBarStub = {
props: ['selectedIds', 'totalResults', 'selectingAll', 'allResultsSelected'],
emits: ['select-all-results', 'select-page', 'clear'],
emits: ['select-all-results', 'select-page', 'clear', 'refresh-token'],
template: `
<div>
<span data-test="selected-count">{{ selectedIds.length }}</span>
@@ -88,6 +90,7 @@ const AccountBulkActionsBarStub = {
<button data-test="select-page" @click="$emit('select-page')">select page</button>
<button data-test="select-all-results" @click="$emit('select-all-results')">select all</button>
<button data-test="clear" @click="$emit('clear')">clear</button>
<button data-test="refresh-token" @click="$emit('refresh-token')">refresh token</button>
</div>
`
}
@@ -104,7 +107,10 @@ const mountView = () => mount(AccountsView, {
TablePageLayout: {
template: '<div><slot name="filters" /><slot name="table" /><slot name="pagination" /></div>'
},
DataTable: { props: ['data'], template: '<div data-test="data-table"></div>' },
DataTable: {
props: ['data'],
template: '<div data-test="data-table"><div v-for="row in data" :key="row.id"><slot name="cell-select" :row="row" /></div></div>'
},
Pagination: true,
ConfirmDialog: true,
AccountTableActions: { template: '<div><slot name="beforeCreate" /><slot name="after" /></div>' },
@@ -139,6 +145,7 @@ describe('admin AccountsView select all filtered results', () => {
localStorage.clear()
listAccounts.mockReset()
listWithEtag.mockReset()
batchRefresh.mockReset()
getBatchTodayStats.mockReset()
getUpstreamBillingProbeSettings.mockReset()
getAllProxies.mockReset()
@@ -156,6 +163,38 @@ describe('admin AccountsView select all filtered results', () => {
getAllGroups.mockResolvedValue([])
})
afterEach(() => {
vi.restoreAllMocks()
})
it.each([
{ name: 'keeps only failed accounts selected', result: { total: 3, success: 2, failed: 1, errors: [{ account_id: 2, error: 'no refresh token available' }] }, expectedIds: [2] },
{ name: 'clears the selection after every account succeeds', result: { total: 3, success: 3, failed: 0 }, expectedIds: [] },
{ name: 'keeps the original selection when failure details are missing', result: { total: 3, success: 2, failed: 1 }, expectedIds: [1, 2, 3] },
])('$name after a batch token refresh and table reload', async ({ result, expectedIds }) => {
listAccounts.mockResolvedValue({ items: makeAccounts(3), total: 3, page: 1, page_size: 20, pages: 1 })
batchRefresh.mockResolvedValue(result)
vi.spyOn(window, 'confirm').mockReturnValue(true)
const wrapper = mountView()
await flushPromises()
await wrapper.get('[data-test="select-page"]').trigger('click')
await wrapper.get('[data-test="refresh-token"]').trigger('click')
await flushPromises()
expect(batchRefresh).toHaveBeenCalledWith([1, 2, 3])
expect(listAccounts).toHaveBeenCalledTimes(2)
expect(wrapper.getComponent(AccountBulkActionsBarStub).props('selectedIds')).toEqual(expectedIds)
expect(wrapper.findAll<HTMLInputElement>('[data-test="data-table"] input').map(input => input.element.checked))
.toEqual([1, 2, 3].map(id => expectedIds.includes(id)))
if (result.failed > 0) {
expect(showError).toHaveBeenCalledWith('admin.accounts.bulkActions.partialSuccess')
await wrapper.get('[data-test="refresh-token"]').trigger('click')
await flushPromises()
expect(batchRefresh).toHaveBeenLastCalledWith(expectedIds)
}
wrapper.unmount()
})
it('selects all matching IDs in one commit and clears the selection when filters change', async () => {
const allAccounts = makeAccounts(45)
listAccounts.mockImplementation(async (_page: number, pageSize: number) => {