fix(keys): sync status after resetting quota

This commit is contained in:
chihao.ou
2026-09-11 19:32:20 +08:00
parent 4726bdd08b
commit a65d476f25
2 changed files with 55 additions and 7 deletions
+9 -5
View File
@@ -1823,14 +1823,18 @@ const setExpirationDays = (days: number) => {
// Reset quota used for an API key
const resetQuotaUsed = async () => {
if (!selectedKey.value) return
const key = selectedKey.value
if (!key) return
showResetQuotaDialog.value = false
try {
await keysAPI.update(selectedKey.value.id, { reset_quota: true })
const updatedKey = await keysAPI.update(key.id, { reset_quota: true })
appStore.showSuccess(t('keys.quotaResetSuccess'))
// Update local state
if (selectedKey.value) {
selectedKey.value.quota_used = 0
key.quota_used = updatedKey.quota_used
if (key.status !== updatedKey.status) {
key.status = updatedKey.status
if (selectedKey.value?.id === key.id) {
formData.value.status = updatedKey.status === 'active' ? 'active' : 'inactive'
}
}
} catch (error: any) {
const errorMsg = error.response?.data?.detail || t('keys.failedToResetQuota')
@@ -7,6 +7,7 @@ import KeysView from '../KeysView.vue'
const {
listKeys,
updateKey,
getPublicSettings,
getDashboardApiKeysUsage,
getAvailableGroups,
@@ -18,6 +19,7 @@ const {
nextStep,
} = vi.hoisted(() => ({
listKeys: vi.fn(),
updateKey: vi.fn(),
getPublicSettings: vi.fn(),
getDashboardApiKeysUsage: vi.fn(),
getAvailableGroups: vi.fn(),
@@ -59,7 +61,7 @@ vi.mock('@/api', () => ({
keysAPI: {
list: listKeys,
create: vi.fn(),
update: vi.fn(),
update: updateKey,
delete: vi.fn(),
toggleStatus: vi.fn(),
},
@@ -170,6 +172,7 @@ const DataTableStub = {
<slot name="cell-id" :value="row.id" :row="row" />
</div>
<slot name="cell-name" :value="row.name" :row="row" />
<slot name="cell-actions" :row="row" />
<div data-test="current-concurrency">
<slot name="cell-current_concurrency" :value="row.current_concurrency" :row="row" />
</div>
@@ -223,7 +226,10 @@ const mountView = async () => {
TablePageLayout: TablePageLayoutStub,
DataTable: DataTableStub,
Pagination: PaginationStub,
BaseDialog: true,
BaseDialog: {
props: ['show'],
template: '<div v-if="show"><slot /><slot name="footer" /></div>',
},
ConfirmDialog: true,
EmptyState: true,
Select: SelectStub,
@@ -261,6 +267,7 @@ describe('user KeysView column settings', () => {
localStorage.clear()
listKeys.mockReset()
updateKey.mockReset()
getPublicSettings.mockReset()
getDashboardApiKeysUsage.mockReset()
getAvailableGroups.mockReset()
@@ -285,6 +292,43 @@ describe('user KeysView column settings', () => {
isCurrentStep.mockReturnValue(false)
})
it.each([
{ initialStatus: 'quota_exhausted', status: 'active', formStatus: 'active' },
{ initialStatus: 'inactive', status: 'inactive', formStatus: 'inactive' },
{ initialStatus: 'active', status: 'active', formStatus: 'inactive' },
] as const)('syncs quota reset from $initialStatus to $status with form status $formStatus', async ({ initialStatus, status, formStatus }) => {
const key: ApiKey = {
...createApiKey(), group_id: 1, quota: 10, quota_used: 10,
status: initialStatus,
}
listKeys.mockResolvedValueOnce({ items: [key], total: 1, page: 1, page_size: 20, pages: 1 })
updateKey.mockResolvedValue({ ...key, status, quota_used: 0 })
const wrapper = await mountView()
await getButtonByText(wrapper, 'common.edit').trigger('click')
await wrapper.get('[data-tour="key-form-name"]').setValue('Unsaved name')
const statusSelect = wrapper.findAllComponents({ name: 'Select' })
.find((select) => select.props('options').length === 2 &&
select.props('options')[0].value === 'active')!
statusSelect.vm.$emit('update:modelValue', 'inactive')
await wrapper.get('button[title="keys.resetQuotaUsed"]').trigger('click')
const confirmation = wrapper.findAllComponents({ name: 'ConfirmDialog' })
.find((dialog) => dialog.props('title') === 'keys.resetQuotaTitle')!
confirmation.vm.$emit('confirm')
await flushPromises()
expect(updateKey).toHaveBeenNthCalledWith(1, key.id, { reset_quota: true })
expect(wrapper.findComponent({ name: 'DataTable' }).props('data')[0])
.toMatchObject({ status, quota_used: 0 })
expect(statusSelect.props('modelValue')).toBe(formStatus)
expect((wrapper.get('[data-tour="key-form-name"]').element as HTMLInputElement).value)
.toBe('Unsaved name')
await wrapper.get('#key-form').trigger('submit')
await flushPromises()
expect(updateKey).toHaveBeenNthCalledWith(2, key.id, expect.objectContaining({ name: 'Unsaved name', status: formStatus }))
wrapper.unmount()
})
it('uses the default API key columns with low-frequency columns hidden', async () => {
const wrapper = await mountView()