Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 209 additions & 0 deletions test/api-error.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { APIError } from '../src/lib/api-error';

describe('APIError', () => {
describe('constructor', () => {
it('should create an APIError with all properties', () => {
const error = new APIError('Test error', 'ERROR_CODE', 404);

expect(error.message).toBe('Test error');
expect(error.error_code).toBe('ERROR_CODE');
expect(error.status).toBe(404);
expect(error.error_message).toBe('Test error');
expect(error.name).toBe('APIError');
expect(error.stack).toBeUndefined();
});

it('should create an APIError with numeric error_code', () => {
const error = new APIError('Test error', 500, 500);

expect(error.error_code).toBe(500);
expect(error.status).toBe(500);
});
});

describe('fromAxiosError', () => {
it('should create APIError from axios error with response data', () => {
const axiosError = {
response: {
data: {
error_message: 'Not Found',
error_code: 404,
},
status: 404,
},
};

const error = APIError.fromAxiosError(axiosError);

expect(error).toBeInstanceOf(APIError);
expect(error.error_message).toBe('Not Found');
expect(error.error_code).toBe(404);
expect(error.status).toBe(404);
});

it('should create APIError from axios error with message but no response', () => {
const axiosError = {
message: 'Network Error',
code: 'ENOTFOUND',
};

const error = APIError.fromAxiosError(axiosError);

expect(error).toBeInstanceOf(APIError);
expect(error.error_message).toBe('Network Error');
expect(error.error_code).toBe('ENOTFOUND');
expect(error.status).toBe(0);
});

it('should create APIError from axios error with message but no code', () => {
const axiosError = {
message: 'Network Error',
};

const error = APIError.fromAxiosError(axiosError);

expect(error).toBeInstanceOf(APIError);
expect(error.error_message).toBe('Network Error');
expect(error.error_code).toBe('NETWORK_ERROR');
expect(error.status).toBe(0);
});

it('should create APIError with default message for unknown errors', () => {
const axiosError = {};

const error = APIError.fromAxiosError(axiosError);

expect(error).toBeInstanceOf(APIError);
expect(error.error_message).toBe('Unknown error occurred');
expect(error.error_code).toBe('UNKNOWN_ERROR');
expect(error.status).toBe(0);
});

it('should handle axios error with response.data but no response.status', () => {
const axiosError = {
response: {
data: {
error_message: 'Server Error',
},
},
};

// This should call fromResponseData, which requires status
// Let's test with a proper status
const axiosErrorWithStatus = {
response: {
data: {
error_message: 'Server Error',
},
status: 500,
},
};

const error = APIError.fromAxiosError(axiosErrorWithStatus);

expect(error).toBeInstanceOf(APIError);
expect(error.error_message).toBe('Server Error');
expect(error.status).toBe(500);
});
});

describe('fromResponseData', () => {
it('should create APIError from response data with error_message', () => {
const responseData = {
error_message: 'Bad Request',
error_code: 400,
};

const error = APIError.fromResponseData(responseData, 400);

expect(error).toBeInstanceOf(APIError);
expect(error.error_message).toBe('Bad Request');
expect(error.error_code).toBe(400);
expect(error.status).toBe(400);
});

it('should create APIError from response data with message fallback', () => {
const responseData = {
message: 'Internal Server Error',
code: 500,
};

const error = APIError.fromResponseData(responseData, 500);

expect(error).toBeInstanceOf(APIError);
expect(error.error_message).toBe('Internal Server Error');
expect(error.error_code).toBe(500);
expect(error.status).toBe(500);
});

it('should create APIError from response data with error fallback', () => {
const responseData = {
error: 'Validation Error',
code: 422,
};

const error = APIError.fromResponseData(responseData, 422);

expect(error).toBeInstanceOf(APIError);
expect(error.error_message).toBe('Validation Error');
expect(error.error_code).toBe(422);
expect(error.status).toBe(422);
});

it('should create APIError from string response data', () => {
const responseData = 'Plain text error message';

const error = APIError.fromResponseData(responseData, 500);

expect(error).toBeInstanceOf(APIError);
expect(error.error_message).toBe('Plain text error message');
expect(error.error_code).toBe(500);
expect(error.status).toBe(500);
});

it('should create APIError with default message when no error fields present', () => {
const responseData = {
someOtherField: 'value',
};

const error = APIError.fromResponseData(responseData, 500);

expect(error).toBeInstanceOf(APIError);
expect(error.error_message).toBe('Request failed');
expect(error.error_code).toBe(500);
expect(error.status).toBe(500);
});

it('should extract error_code from response data', () => {
const responseData = {
error_message: 'Error',
error_code: 999,
};

const error = APIError.fromResponseData(responseData, 500);

expect(error.error_code).toBe(999);
});

it('should extract code from response data when error_code not present', () => {
const responseData = {
error_message: 'Error',
code: 888,
};

const error = APIError.fromResponseData(responseData, 500);

expect(error.error_code).toBe(888);
});

it('should use status as error_code fallback', () => {
const responseData = {
error_message: 'Error',
};

const error = APIError.fromResponseData(responseData, 503);

expect(error.error_code).toBe(503);
});
});
});
5 changes: 5 additions & 0 deletions test/contentstack-core.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ describe('contentstackCore', () => {

describe('config.onError', () => {
it('should call the onError function when an error occurs', async () => {
// Suppress expected console.error from network error
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();

const onError = jest.fn();
const options = {
defaultHostname: 'cdn.contentstack.io',
Expand All @@ -163,6 +166,8 @@ describe('contentstackCore', () => {
} catch (error: unknown) {
expect(onError).toBeCalledWith(error);
}

consoleErrorSpy.mockRestore();
});

it('should not call the onError function when no error occurs', async () => {
Expand Down
30 changes: 30 additions & 0 deletions test/request.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,4 +344,34 @@ describe('Request tests', () => {
const result = await getData(client, url, requestData);
expect(result).toEqual(mockResponse);
});

it('should use instance.request when URL length exceeds 2000 characters', async () => {
const client = httpClient({ defaultHostname: 'example.com' });
const url = '/your-api-endpoint';
const mockResponse = { data: 'mocked' };

// Create a very long query parameter that will exceed 2000 characters when combined with baseURL
// baseURL is typically like 'https://example.com:443/v3' (~30 chars), url is '/your-api-endpoint' (~20 chars)
// So we need params that serialize to >1950 chars to exceed 2000 total
const longParam = 'x'.repeat(2000);
const requestData = { params: { longParam, param2: 'y'.repeat(500) } };

// Mock instance.request since that's what gets called for long URLs
const requestSpy = jest.spyOn(client, 'request').mockResolvedValue({ data: mockResponse } as any);

const result = await getData(client, url, requestData);

expect(result).toEqual(mockResponse);
// Verify that request was called (not get) with the full URL
expect(requestSpy).toHaveBeenCalledWith(
expect.objectContaining({
method: 'get',
url: expect.stringMatching(/longParam/),
maxContentLength: Infinity,
maxBodyLength: Infinity,
})
);

requestSpy.mockRestore();
});
});
Loading
Loading