From 4767c23864e053f9b26d16b31977d5d5e2cbf44c Mon Sep 17 00:00:00 2001 From: Roman Onishchenko Date: Mon, 27 Apr 2026 12:46:07 +0200 Subject: [PATCH] test(client): define validation retry behavior --- packages/client/src/core/should-retry.ts | 1 + .../integration/response-validation.test.ts | 28 +++++++++++++++++++ .../client/tests/unit/should-retry.test.ts | 23 +++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/packages/client/src/core/should-retry.ts b/packages/client/src/core/should-retry.ts index 428e06c..c0b4192 100644 --- a/packages/client/src/core/should-retry.ts +++ b/packages/client/src/core/should-retry.ts @@ -37,5 +37,6 @@ export function shouldRetry({ attempt, method, retry, error }: ShouldRetryParams return false; } + // Non-transient errors (e.g. validation failures) are not retried. return false; } diff --git a/packages/client/tests/integration/response-validation.test.ts b/packages/client/tests/integration/response-validation.test.ts index e0b2fb5..9a660cb 100644 --- a/packages/client/tests/integration/response-validation.test.ts +++ b/packages/client/tests/integration/response-validation.test.ts @@ -92,4 +92,32 @@ describe('response validation', () => { await expect(client.get('/users/1')).resolves.toEqual({ id: 'user-1' }); }); + + it('does not retry when response validation fails', async () => { + const fetchMock = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ name: 'Roman' }), { + status: 200, + headers: { + 'content-type': 'application/json', + }, + }), + ); + + const client = createClient({ + baseUrl: 'https://api.example.com', + fetch: fetchMock, + retry: { + attempts: 3, + retryOn: ['network-error', '5xx', '429'], + retryMethods: ['GET'], + }, + validateResponse(data) { + return typeof data === 'object' && data !== null && 'id' in data; + }, + }); + + await expect(client.get('/users/1')).rejects.toBeInstanceOf(ValidationError); + + expect(fetchMock).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/client/tests/unit/should-retry.test.ts b/packages/client/tests/unit/should-retry.test.ts index 0ca4709..1239cfe 100644 --- a/packages/client/tests/unit/should-retry.test.ts +++ b/packages/client/tests/unit/should-retry.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from 'vitest'; import { HttpError } from '../../src/errors/http-error'; import { NetworkError } from '../../src/errors/network-error'; +import { ValidationError } from '../../src/errors/validation-error'; import { RequestAbortedError } from '../../src/errors/request-aborted-error'; import { shouldRetry } from '../../src/core/should-retry'; import type { RetryConfig } from '../../src/types/config'; @@ -23,6 +24,18 @@ function createHttpError(status: number, statusText = 'Error'): HttpError { return new HttpError(response); } +function createValidationError(): ValidationError { + const response = new Response(JSON.stringify({ name: 'Roman' }), { + status: 200, + statusText: 'OK', + headers: { + 'content-type': 'application/json', + }, + }); + + return new ValidationError(response, { name: 'Roman' }); +} + function createParams( overrides: Partial<{ attempt: number; @@ -131,4 +144,14 @@ describe('shouldRetry', () => { ), ).toBe(false); }); + + it('does not retry validation errors', () => { + expect( + shouldRetry( + createParams({ + error: createValidationError(), + }), + ), + ).toBe(false); + }); });