✅(frontend) Improve test coverage
Improve the test coverage of the "api" modules. Signed-off-by: Zorin95670 <moittie.vincent@gmail.com>
This commit is contained in:
@@ -16,6 +16,7 @@ and this project adheres to
|
|||||||
## Changed
|
## Changed
|
||||||
|
|
||||||
- 📝(frontend) Update documentation
|
- 📝(frontend) Update documentation
|
||||||
|
- ✅(frontend) Improve tests coverage
|
||||||
|
|
||||||
## [3.2.1] - 2025-05-06
|
## [3.2.1] - 2025-05-06
|
||||||
|
|
||||||
|
|||||||
36
src/frontend/apps/impress/src/api/__tests__/APIError.test.ts
Normal file
36
src/frontend/apps/impress/src/api/__tests__/APIError.test.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { APIError, isAPIError } from '@/api';
|
||||||
|
|
||||||
|
describe('APIError', () => {
|
||||||
|
it('should correctly instantiate with required fields', () => {
|
||||||
|
const error = new APIError('Something went wrong', { status: 500 });
|
||||||
|
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
expect(error).toBeInstanceOf(APIError);
|
||||||
|
expect(error.message).toBe('Something went wrong');
|
||||||
|
expect(error.status).toBe(500);
|
||||||
|
expect(error.cause).toBeUndefined();
|
||||||
|
expect(error.data).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly instantiate with all fields', () => {
|
||||||
|
const details = { field: 'email' };
|
||||||
|
const error = new APIError('Validation failed', {
|
||||||
|
status: 400,
|
||||||
|
cause: ['Invalid email format'],
|
||||||
|
data: details,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(error.name).toBe('APIError');
|
||||||
|
expect(error.status).toBe(400);
|
||||||
|
expect(error.cause).toEqual(['Invalid email format']);
|
||||||
|
expect(error.data).toEqual(details);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be detected by isAPIError type guard', () => {
|
||||||
|
const error = new APIError('Unauthorized', { status: 401 });
|
||||||
|
const notAnError = { message: 'Fake error' };
|
||||||
|
|
||||||
|
expect(isAPIError(error)).toBe(true);
|
||||||
|
expect(isAPIError(notAnError)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
16
src/frontend/apps/impress/src/api/__tests__/config.test.ts
Normal file
16
src/frontend/apps/impress/src/api/__tests__/config.test.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { baseApiUrl } from '@/api';
|
||||||
|
|
||||||
|
describe('config', () => {
|
||||||
|
it('constructs URL with default version', () => {
|
||||||
|
expect(baseApiUrl()).toBe('http://test.jest/api/v1.0/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('constructs URL with custom version', () => {
|
||||||
|
expect(baseApiUrl('2.0')).toBe('http://test.jest/api/v2.0/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses env origin if available', () => {
|
||||||
|
process.env.NEXT_PUBLIC_API_ORIGIN = 'https://env.example.com';
|
||||||
|
expect(baseApiUrl('3.0')).toBe('https://env.example.com/api/v3.0/');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -36,4 +36,13 @@ describe('fetchAPI', () => {
|
|||||||
|
|
||||||
expect(fetchMock.lastUrl()).toEqual('http://test.jest/api/v2.0/some/url');
|
expect(fetchMock.lastUrl()).toEqual('http://test.jest/api/v2.0/some/url');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('removes Content-Type header when withoutContentType is true', async () => {
|
||||||
|
fetchMock.mock('http://test.jest/api/v1.0/some/url', 200);
|
||||||
|
|
||||||
|
await fetchAPI('some/url', { withoutContentType: true });
|
||||||
|
|
||||||
|
const options = fetchMock.lastOptions();
|
||||||
|
expect(options?.headers).not.toHaveProperty('Content-Type');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
59
src/frontend/apps/impress/src/api/__tests__/helpers.test.tsx
Normal file
59
src/frontend/apps/impress/src/api/__tests__/helpers.test.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
import { renderHook, waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { useAPIInfiniteQuery } from '@/api';
|
||||||
|
|
||||||
|
interface DummyItem {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DummyResponse {
|
||||||
|
results: DummyItem[];
|
||||||
|
next?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createWrapper = () => {
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
return ({ children }: { children: React.ReactNode }) => (
|
||||||
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('helpers', () => {
|
||||||
|
it('fetches and paginates correctly', async () => {
|
||||||
|
const mockAPI = jest
|
||||||
|
.fn<Promise<DummyResponse>, [{ page: number; query: string }]>()
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
results: [{ id: 1 }],
|
||||||
|
next: 'url?page=2',
|
||||||
|
})
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
results: [{ id: 2 }],
|
||||||
|
next: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = renderHook(
|
||||||
|
() => useAPIInfiniteQuery('test-key', mockAPI, { query: 'test' }),
|
||||||
|
{ wrapper: createWrapper() },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for first page
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.data?.pages[0].results[0].id).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch next page
|
||||||
|
await result.current.fetchNextPage();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.data?.pages.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.data?.pages[1].results[0].id).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockAPI).toHaveBeenCalledWith({ query: 'test', page: 1 });
|
||||||
|
expect(mockAPI).toHaveBeenCalledWith({ query: 'test', page: 2 });
|
||||||
|
});
|
||||||
|
});
|
||||||
57
src/frontend/apps/impress/src/api/__tests__/utils.test.ts
Normal file
57
src/frontend/apps/impress/src/api/__tests__/utils.test.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { errorCauses, getCSRFToken } from '@/api';
|
||||||
|
|
||||||
|
describe('utils', () => {
|
||||||
|
describe('errorCauses', () => {
|
||||||
|
const createMockResponse = (jsonData: any, status = 400): Response => {
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
json: () => jsonData,
|
||||||
|
} as unknown as Response;
|
||||||
|
};
|
||||||
|
|
||||||
|
it('parses multiple string causes from error body', async () => {
|
||||||
|
const mockResponse = createMockResponse(
|
||||||
|
{
|
||||||
|
field: ['error message 1', 'error message 2'],
|
||||||
|
},
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await errorCauses(mockResponse, { context: 'login' });
|
||||||
|
|
||||||
|
expect(result.status).toBe(400);
|
||||||
|
expect(result.cause).toEqual(['error message 1', 'error message 2']);
|
||||||
|
expect(result.data).toEqual({ context: 'login' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns undefined causes if no JSON body', async () => {
|
||||||
|
const mockResponse = createMockResponse(null, 500);
|
||||||
|
|
||||||
|
const result = await errorCauses(mockResponse);
|
||||||
|
|
||||||
|
expect(result.status).toBe(500);
|
||||||
|
expect(result.cause).toBeUndefined();
|
||||||
|
expect(result.data).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCSRFToken', () => {
|
||||||
|
it('extracts csrftoken from document.cookie', () => {
|
||||||
|
Object.defineProperty(document, 'cookie', {
|
||||||
|
writable: true,
|
||||||
|
value: 'sessionid=xyz; csrftoken=abc123; theme=dark',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getCSRFToken()).toBe('abc123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns undefined if csrftoken is not present', () => {
|
||||||
|
Object.defineProperty(document, 'cookie', {
|
||||||
|
writable: true,
|
||||||
|
value: 'sessionid=xyz; theme=dark',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getCSRFToken()).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user