UNPKG

@rtk-query/graphql-request-base-query

Version:
451 lines (383 loc) 12.6 kB
import { describe, it, expect, vi } from 'vitest' import type { GraphQLClient } from 'graphql-request' import { ClientError } from 'graphql-request' import { graphqlRequestBaseQuery } from './index' import type { BaseQueryApi } from '@reduxjs/toolkit/query' describe('graphqlRequestBaseQuery', () => { const mockGetState = vi.fn() const mockDispatch = vi.fn() const mockAbort = vi.fn() const mockExtra = {} const mockSignal = new AbortController().signal const createMockApi = (): BaseQueryApi => ({ signal: mockSignal, abort: mockAbort, dispatch: mockDispatch, getState: mockGetState, extra: mockExtra, endpoint: 'testEndpoint', type: 'query' as const, forced: false, }) describe('successful requests', () => { it('returns data on successful request', async () => { const mockData = { user: { id: '1', name: 'Test User' } } const mockClient = { request: vi.fn().mockResolvedValue(mockData), } as unknown as GraphQLClient const baseQuery = graphqlRequestBaseQuery({ client: mockClient }) const result = await baseQuery( { document: 'query { user { id name } }', variables: {}, }, createMockApi(), {}, ) expect(result).toEqual({ data: mockData, meta: {}, }) expect(mockClient.request).toHaveBeenCalledTimes(1) }) it('passes variables to the GraphQL client', async () => { const mockData = { user: { id: '1' } } const mockClient = { request: vi.fn().mockResolvedValue(mockData), } as unknown as GraphQLClient const baseQuery = graphqlRequestBaseQuery({ client: mockClient }) const variables = { userId: '1' } await baseQuery( { document: 'query($userId: ID!) { user(id: $userId) { id } }', variables, }, createMockApi(), {}, ) expect(mockClient.request).toHaveBeenCalledWith( expect.objectContaining({ variables, }), ) }) it('passes document to the GraphQL client', async () => { const mockData = { test: 'data' } const mockClient = { request: vi.fn().mockResolvedValue(mockData), } as unknown as GraphQLClient const baseQuery = graphqlRequestBaseQuery({ client: mockClient }) const document = 'query { test }' await baseQuery( { document, }, createMockApi(), {}, ) expect(mockClient.request).toHaveBeenCalledWith( expect.objectContaining({ document, }), ) }) }) describe('error handling', () => { it('returns error for ClientError (GraphQL API errors)', async () => { const mockError = new ClientError( { errors: [{ message: 'User not found' }], } as any, { query: 'test query' } as any, ) const mockClient = { request: vi.fn().mockRejectedValue(mockError), } as unknown as GraphQLClient const baseQuery = graphqlRequestBaseQuery({ client: mockClient }) const result = await baseQuery( { document: 'query { user { id } }', }, createMockApi(), {}, ) expect(result).toEqual({ error: { name: expect.any(String), message: expect.any(String), stack: expect.any(String), }, meta: { request: expect.any(Object), response: expect.any(Object), }, }) }) it('includes request and response in meta for ClientError', async () => { const mockRequest = { query: 'test query' } const mockResponse = { errors: [{ message: 'Error' }] } const mockError = new ClientError(mockResponse as any, mockRequest as any) const mockClient = { request: vi.fn().mockRejectedValue(mockError), } as unknown as GraphQLClient const baseQuery = graphqlRequestBaseQuery({ client: mockClient }) const result = await baseQuery( { document: 'query { test }', }, createMockApi(), {}, ) expect(result.meta).toHaveProperty('request') expect(result.meta).toHaveProperty('response') }) it('returns network errors (non-ClientError) instead of throwing', async () => { const networkError = new Error('Network timeout') const mockClient = { request: vi.fn().mockRejectedValue(networkError), } as unknown as GraphQLClient const baseQuery = graphqlRequestBaseQuery({ client: mockClient }) const result = await baseQuery( { document: 'query { user { id } }', }, createMockApi(), {}, ) // Should return error instead of throwing expect(result).toEqual({ error: { name: 'Error', message: 'Network timeout', stack: expect.any(String), }, meta: {}, }) }) it('returns fetch errors instead of throwing', async () => { const fetchError = new Error('Failed to fetch') const mockClient = { request: vi.fn().mockRejectedValue(fetchError), } as unknown as GraphQLClient const baseQuery = graphqlRequestBaseQuery({ client: mockClient }) const result = await baseQuery( { document: 'query { test }', }, createMockApi(), {}, ) // Should return error instead of throwing expect(result).toEqual({ error: { name: 'Error', message: 'Failed to fetch', stack: expect.any(String), }, meta: {}, }) }) it('uses custom error handler when provided', async () => { const mockError = new ClientError( { errors: [{ message: 'Custom error' }], } as any, { query: 'test query' } as any, ) const mockClient = { request: vi.fn().mockRejectedValue(mockError), } as unknown as GraphQLClient const customErrors = vi.fn((error: ClientError) => ({ customField: 'custom value', originalMessage: error.message, })) const baseQuery = graphqlRequestBaseQuery({ client: mockClient, customErrors, }) const result = await baseQuery( { document: 'query { user { id } }', }, createMockApi(), {}, ) expect(customErrors).toHaveBeenCalledWith(mockError) expect(result.error).toEqual({ customField: 'custom value', originalMessage: expect.any(String), }) }) it('uses default error format when customErrors not provided', async () => { const mockError = new ClientError( { errors: [{ message: 'Test error' }], } as any, { query: 'test query' } as any, ) const mockClient = { request: vi.fn().mockRejectedValue(mockError), } as unknown as GraphQLClient const baseQuery = graphqlRequestBaseQuery({ client: mockClient }) const result = await baseQuery( { document: 'query { test }', }, createMockApi(), {}, ) expect(result).toEqual({ error: { name: expect.any(String), message: expect.any(String), stack: expect.any(String), }, meta: { request: expect.any(Object), response: expect.any(Object), }, }) }) }) describe('headers', () => { it('applies prepareHeaders function', async () => { const mockData = { test: 'data' } const mockClient = { request: vi.fn().mockResolvedValue(mockData), } as unknown as GraphQLClient const prepareHeaders = vi.fn((headers: Headers) => { headers.set('Authorization', 'Bearer token123') return headers }) const baseQuery = graphqlRequestBaseQuery({ client: mockClient, prepareHeaders, }) await baseQuery( { document: 'query { test }', }, createMockApi(), {}, ) expect(prepareHeaders).toHaveBeenCalled() expect(mockClient.request).toHaveBeenCalledWith( expect.objectContaining({ requestHeaders: expect.any(Headers), }), ) }) it('passes correct arguments to prepareHeaders', async () => { const mockData = { test: 'data' } const mockClient = { request: vi.fn().mockResolvedValue(mockData), } as unknown as GraphQLClient const prepareHeaders = vi.fn((headers: Headers) => headers) const baseQuery = graphqlRequestBaseQuery({ client: mockClient, prepareHeaders, }) const mockApi = createMockApi() await baseQuery( { document: 'query { test }', }, mockApi, {}, ) expect(prepareHeaders).toHaveBeenCalledWith( expect.any(Headers), expect.objectContaining({ getState: mockApi.getState, endpoint: mockApi.endpoint, forced: mockApi.forced, type: mockApi.type, extra: mockApi.extra, }), ) }) it('merges requestHeaders with prepareHeaders', async () => { const mockData = { test: 'data' } const mockClient = { request: vi.fn().mockResolvedValue(mockData), } as unknown as GraphQLClient const prepareHeaders = vi.fn((headers: Headers) => { headers.set('X-Custom-Header', 'custom-value') return headers }) const baseQuery = graphqlRequestBaseQuery({ client: mockClient, requestHeaders: { 'Content-Type': 'application/json', }, prepareHeaders, }) await baseQuery( { document: 'query { test }', }, createMockApi(), {}, ) const callArgs = (mockClient.request as any).mock.calls[0][0] const headers = callArgs.requestHeaders as Headers expect(headers.get('Content-Type')).toBe('application/json') expect(headers.get('X-Custom-Header')).toBe('custom-value') }) }) describe('client initialization', () => { it('creates GraphQLClient from URL when client not provided', async () => { const mockData = { test: 'data' } // We can't easily test the actual client creation without mocking the constructor, // but we can verify the baseQuery works with a URL const baseQuery = graphqlRequestBaseQuery({ url: 'https://api.example.com/graphql', }) // This will fail in the test environment since there's no actual server, // but it verifies the configuration is accepted expect(baseQuery).toBeDefined() expect(typeof baseQuery).toBe('function') }) it('uses provided client when available', async () => { const mockData = { test: 'data' } const mockClient = { request: vi.fn().mockResolvedValue(mockData), } as unknown as GraphQLClient const baseQuery = graphqlRequestBaseQuery({ client: mockClient }) await baseQuery( { document: 'query { test }', }, createMockApi(), {}, ) expect(mockClient.request).toHaveBeenCalled() }) }) describe('signal handling', () => { it('passes abort signal to GraphQL client', async () => { const mockData = { test: 'data' } const mockClient = { request: vi.fn().mockResolvedValue(mockData), } as unknown as GraphQLClient const baseQuery = graphqlRequestBaseQuery({ client: mockClient }) const controller = new AbortController() const mockApi = { ...createMockApi(), signal: controller.signal, } await baseQuery( { document: 'query { test }', }, mockApi, {}, ) expect(mockClient.request).toHaveBeenCalledWith( expect.objectContaining({ signal: controller.signal, }), ) }) }) })