refactor: split retry+diagnostics tests, drop redundant useRemoteCombo return interface

- Split retryAndDiagnostics.test.ts into retry.test.ts + diagnostics.test.ts so each test file mirrors its source file
- Add explicit ERR_CANCELED test case in retry.test.ts
- Drop UseRemoteComboResult interface — useRemoteCombo has a single caller, inferring the return shape removes redundant type definition (per @christian-byrne)
This commit is contained in:
Glary-Bot
2026-05-21 23:12:38 +00:00
parent a612506a9e
commit 25d40db2aa
3 changed files with 73 additions and 80 deletions

View File

@@ -1,71 +1,8 @@
import { AxiosError, AxiosHeaders } from 'axios'
import { describe, expect, it } from 'vitest'
import { getBackoff, isRetriableError } from '@/base/remote/retry'
import { summarizeError, summarizePayload } from '@/base/remote/diagnostics'
describe('getBackoff', () => {
it('grows exponentially from 1s', () => {
expect(getBackoff(1)).toBe(2000)
expect(getBackoff(2)).toBe(4000)
expect(getBackoff(3)).toBe(8000)
expect(getBackoff(4)).toBe(16000)
})
it('caps at 16s for higher attempt counts', () => {
expect(getBackoff(5)).toBe(16000)
expect(getBackoff(10)).toBe(16000)
expect(getBackoff(100)).toBe(16000)
})
})
describe('isRetriableError', () => {
function axiosErrorWithStatus(status: number): AxiosError {
return new AxiosError(
`HTTP ${status}`,
'ERR_BAD_RESPONSE',
undefined,
undefined,
{
status,
statusText: '',
headers: {},
config: { headers: new AxiosHeaders() },
data: null
}
)
}
it('retries non-axios errors (e.g. unexpected throws)', () => {
expect(isRetriableError(new Error('boom'))).toBe(true)
expect(isRetriableError('string error')).toBe(true)
expect(isRetriableError(undefined)).toBe(true)
})
it('retries axios errors with no response (network failures)', () => {
const err = new AxiosError('Network Error', 'ERR_NETWORK')
expect(isRetriableError(err)).toBe(true)
})
it('retries 5xx responses', () => {
expect(isRetriableError(axiosErrorWithStatus(500))).toBe(true)
expect(isRetriableError(axiosErrorWithStatus(502))).toBe(true)
expect(isRetriableError(axiosErrorWithStatus(503))).toBe(true)
})
it('retries 408 (request timeout) and 429 (too many requests)', () => {
expect(isRetriableError(axiosErrorWithStatus(408))).toBe(true)
expect(isRetriableError(axiosErrorWithStatus(429))).toBe(true)
})
it('does not retry other 4xx responses', () => {
expect(isRetriableError(axiosErrorWithStatus(400))).toBe(false)
expect(isRetriableError(axiosErrorWithStatus(401))).toBe(false)
expect(isRetriableError(axiosErrorWithStatus(403))).toBe(false)
expect(isRetriableError(axiosErrorWithStatus(404))).toBe(false)
})
})
describe('summarizeError', () => {
it('extracts message, code and status from an axios error', () => {
const err = new AxiosError(

View File

@@ -0,0 +1,71 @@
import { AxiosError, AxiosHeaders } from 'axios'
import { describe, expect, it } from 'vitest'
import { getBackoff, isRetriableError } from '@/base/remote/retry'
describe('getBackoff', () => {
it('grows exponentially from 1s', () => {
expect(getBackoff(1)).toBe(2000)
expect(getBackoff(2)).toBe(4000)
expect(getBackoff(3)).toBe(8000)
expect(getBackoff(4)).toBe(16000)
})
it('caps at 16s for higher attempt counts', () => {
expect(getBackoff(5)).toBe(16000)
expect(getBackoff(10)).toBe(16000)
expect(getBackoff(100)).toBe(16000)
})
})
describe('isRetriableError', () => {
function axiosErrorWithStatus(status: number): AxiosError {
return new AxiosError(
`HTTP ${status}`,
'ERR_BAD_RESPONSE',
undefined,
undefined,
{
status,
statusText: '',
headers: {},
config: { headers: new AxiosHeaders() },
data: null
}
)
}
it('retries non-axios errors (e.g. unexpected throws)', () => {
expect(isRetriableError(new Error('boom'))).toBe(true)
expect(isRetriableError('string error')).toBe(true)
expect(isRetriableError(undefined)).toBe(true)
})
it('retries axios errors with no response (network failures)', () => {
const err = new AxiosError('Network Error', 'ERR_NETWORK')
expect(isRetriableError(err)).toBe(true)
})
it('does not retry canceled axios requests (ERR_CANCELED)', () => {
const err = new AxiosError('canceled', 'ERR_CANCELED')
expect(isRetriableError(err)).toBe(false)
})
it('retries 5xx responses', () => {
expect(isRetriableError(axiosErrorWithStatus(500))).toBe(true)
expect(isRetriableError(axiosErrorWithStatus(502))).toBe(true)
expect(isRetriableError(axiosErrorWithStatus(503))).toBe(true)
})
it('retries 408 (request timeout) and 429 (too many requests)', () => {
expect(isRetriableError(axiosErrorWithStatus(408))).toBe(true)
expect(isRetriableError(axiosErrorWithStatus(429))).toBe(true)
})
it('does not retry other 4xx responses', () => {
expect(isRetriableError(axiosErrorWithStatus(400))).toBe(false)
expect(isRetriableError(axiosErrorWithStatus(401))).toBe(false)
expect(isRetriableError(axiosErrorWithStatus(403))).toBe(false)
expect(isRetriableError(axiosErrorWithStatus(404))).toBe(false)
})
})

View File

@@ -1,5 +1,5 @@
import { computed, ref, toValue, watch } from 'vue'
import type { ComputedRef, MaybeRefOrGetter, Ref } from 'vue'
import type { MaybeRefOrGetter, Ref } from 'vue'
import { useI18n } from 'vue-i18n'
import {
@@ -23,22 +23,7 @@ interface UseRemoteComboArgs {
enabled?: MaybeRefOrGetter<boolean>
}
interface UseRemoteComboResult {
isOpen: Ref<boolean>
searchQuery: Ref<string>
items: ComputedRef<DropdownItemShape[]>
filteredItems: ComputedRef<DropdownItemShape[]>
isLoading: ComputedRef<boolean>
isFetching: ComputedRef<boolean>
errorMessage: ComputedRef<string | null>
refresh: () => Promise<void>
select: (id: string) => void
selectedValue: Ref<string | undefined>
fieldLabel: ComputedRef<string>
previewType: ComputedRef<RemoteComboPreviewType>
}
export function useRemoteCombo(args: UseRemoteComboArgs): UseRemoteComboResult {
export function useRemoteCombo(args: UseRemoteComboArgs) {
const { t } = useI18n()
const isOpen = ref(false)
const searchQuery = ref('')