From 25d40db2aa9c7cfcf4bfac84f2513dd8a1dbc9f6 Mon Sep 17 00:00:00 2001 From: Glary-Bot Date: Thu, 21 May 2026 23:12:38 +0000 Subject: [PATCH] refactor: split retry+diagnostics tests, drop redundant useRemoteCombo return interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- ...iagnostics.test.ts => diagnostics.test.ts} | 63 ---------------- src/base/remote/retry.test.ts | 71 +++++++++++++++++++ .../widgets/composables/useRemoteCombo.ts | 19 +---- 3 files changed, 73 insertions(+), 80 deletions(-) rename src/base/remote/{retryAndDiagnostics.test.ts => diagnostics.test.ts} (64%) create mode 100644 src/base/remote/retry.test.ts diff --git a/src/base/remote/retryAndDiagnostics.test.ts b/src/base/remote/diagnostics.test.ts similarity index 64% rename from src/base/remote/retryAndDiagnostics.test.ts rename to src/base/remote/diagnostics.test.ts index a13ef926d3..d71cae4518 100644 --- a/src/base/remote/retryAndDiagnostics.test.ts +++ b/src/base/remote/diagnostics.test.ts @@ -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( diff --git a/src/base/remote/retry.test.ts b/src/base/remote/retry.test.ts new file mode 100644 index 0000000000..641f46b928 --- /dev/null +++ b/src/base/remote/retry.test.ts @@ -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) + }) +}) diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useRemoteCombo.ts b/src/renderer/extensions/vueNodes/widgets/composables/useRemoteCombo.ts index 74d6ec40a6..a729956515 100644 --- a/src/renderer/extensions/vueNodes/widgets/composables/useRemoteCombo.ts +++ b/src/renderer/extensions/vueNodes/widgets/composables/useRemoteCombo.ts @@ -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 } -interface UseRemoteComboResult { - isOpen: Ref - searchQuery: Ref - items: ComputedRef - filteredItems: ComputedRef - isLoading: ComputedRef - isFetching: ComputedRef - errorMessage: ComputedRef - refresh: () => Promise - select: (id: string) => void - selectedValue: Ref - fieldLabel: ComputedRef - previewType: ComputedRef -} - -export function useRemoteCombo(args: UseRemoteComboArgs): UseRemoteComboResult { +export function useRemoteCombo(args: UseRemoteComboArgs) { const { t } = useI18n() const isOpen = ref(false) const searchQuery = ref('')