add unit tests
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-02-24 11:14:24 +01:00
parent 7e938138ff
commit d7e144620e
23 changed files with 2766 additions and 17 deletions

View File

@@ -0,0 +1,154 @@
import { describe, expect, it, vi } from 'vitest';
import { ApiError, createApiClient } from '../../src/api/createApiClient';
describe('createApiClient', () => {
it('sends json requests and returns parsed payload', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: vi.fn().mockResolvedValue({ ok: true }),
} as unknown as Response);
const client = createApiClient({
baseUrl: 'https://api.example.com',
fetchImpl: fetchMock as typeof fetch,
});
const result = await client.request<{ ok: boolean }>('/users', {
method: 'POST',
token: 'token-123',
body: { hello: 'world' },
});
expect(result).toEqual({ ok: true });
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock).toHaveBeenCalledWith('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer token-123',
},
body: JSON.stringify({ hello: 'world' }),
});
});
it('maps api error payload through custom resolver', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: false,
status: 401,
json: vi.fn().mockResolvedValue({
code: 'AUTH_UNAUTHORIZED',
error: 'unauthorized',
requestId: 'req-1',
details: { reason: 'expired' },
}),
} as unknown as Response);
const resolveError = vi.fn(() => 'Unauthorized access. Please sign in again.');
const client = createApiClient({
baseUrl: 'https://api.example.com',
fetchImpl: fetchMock as typeof fetch,
resolveError,
});
await expect(client.request('/users')).rejects.toMatchObject({
name: 'ApiError',
status: 401,
code: 'AUTH_UNAUTHORIZED',
requestId: 'req-1',
details: { reason: 'expired' },
rawMessage: 'unauthorized',
message: 'Unauthorized access. Please sign in again.',
});
expect(resolveError).toHaveBeenCalledWith({
code: 'AUTH_UNAUTHORIZED',
status: 401,
fallbackMessage: 'unauthorized',
});
});
it('infers an error code from status when payload has no code', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: false,
status: 404,
json: vi.fn().mockResolvedValue({ error: 'missing resource' }),
} as unknown as Response);
const inferErrorCodeFromStatus = vi.fn((status?: number | null) =>
status === 404 ? 'USER_NOT_FOUND' : undefined,
);
const client = createApiClient({
baseUrl: 'https://api.example.com',
fetchImpl: fetchMock as typeof fetch,
inferErrorCodeFromStatus,
resolveError: ({ code }) => (code === 'USER_NOT_FOUND' ? 'User not found.' : 'Unknown'),
});
await expect(client.request('/users/missing')).rejects.toMatchObject({
code: 'USER_NOT_FOUND',
message: 'User not found.',
});
});
it('falls back to default messages when response is not valid json', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: false,
status: 500,
json: vi.fn().mockRejectedValue(new Error('invalid json')),
} as unknown as Response);
const client = createApiClient({
baseUrl: 'https://api.example.com',
fetchImpl: fetchMock as typeof fetch,
});
await expect(client.request('/users')).rejects.toMatchObject({
code: undefined,
rawMessage: undefined,
message: 'Request failed (500).',
});
});
it('uses generic default message when status is unavailable', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: false,
status: undefined,
json: vi.fn().mockResolvedValue(null),
} as unknown as Response);
const client = createApiClient({
baseUrl: 'https://api.example.com',
fetchImpl: fetchMock as typeof fetch,
});
let thrown: unknown;
try {
await client.request('/users');
} catch (err) {
thrown = err;
}
expect(thrown).toBeInstanceOf(ApiError);
expect((thrown as ApiError).message).toBe('Request failed. Please try again.');
});
it('uses raw fallback error text with the default resolver when present', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: false,
status: 400,
json: vi.fn().mockResolvedValue({
error: 'Validation failed in backend.',
}),
} as unknown as Response);
const client = createApiClient({
baseUrl: 'https://api.example.com',
fetchImpl: fetchMock as typeof fetch,
});
await expect(client.request('/users')).rejects.toMatchObject({
message: 'Validation failed in backend.',
rawMessage: 'Validation failed in backend.',
});
});
});