@eleva-io/erp-sdk
Version:
SDK oficial para el ERP de Eleva
203 lines • 10.5 kB
JavaScript
import { ElevaError, ERROR_CODES, ErrorKind } from '.';
import { RemoteElevaError } from './errors_remote';
const request = {
operation: 'GET',
metadata: { authorization: 'Bearer token' },
resource: '/api/users/1',
payload: undefined,
};
const response = {
statusCode: 404,
metadata: { 'content-type': 'application/json' },
payload: { code: 'NOT_FOUND', message: 'Not found', kind: 'domain', status: 404 },
};
const validBody = {
code: ERROR_CODES.NOT_FOUND,
kind: ErrorKind.DOMAIN,
status: 404,
message: 'User not found',
details: { entity: 'User' },
};
describe('RemoteElevaError', () => {
describe('constructor', () => {
it('is an instance of Error, ElevaError and RemoteElevaError', () => {
const error = RemoteElevaError.from({ code: ERROR_CODES.INTERNAL_SERVER_ERROR }, request, response);
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(ElevaError);
expect(error).toBeInstanceOf(RemoteElevaError);
});
it('sets name to "RemoteElevaError"', () => {
const error = RemoteElevaError.from({ code: ERROR_CODES.INTERNAL_SERVER_ERROR }, request, response);
expect(error.name).toBe('RemoteElevaError');
});
it('exposes request and response as readonly properties', () => {
const error = RemoteElevaError.from({ code: ERROR_CODES.INTERNAL_SERVER_ERROR }, request, response);
expect(error.request).toBe(request);
expect(error.response).toBe(response);
});
it('inherits code, kind, status and message from the catalog', () => {
const error = RemoteElevaError.from({
code: ERROR_CODES.NOT_FOUND,
}, request, response);
expect(error.code).toBe(ERROR_CODES.NOT_FOUND);
expect(error.kind).toBe(ErrorKind.DOMAIN);
expect(error.status).toBe(404);
});
it('forwards cause to the underlying Error', () => {
const cause = new Error('network timeout');
const error = RemoteElevaError.from({ code: ERROR_CODES.INTERNAL_SERVER_ERROR }, request, response, cause);
expect(error.cause).toBe(cause);
});
});
describe('is()', () => {
it('returns true when the code matches', () => {
const error = RemoteElevaError.from({
code: ERROR_CODES.NOT_FOUND,
}, request, response);
expect(error.is(ERROR_CODES.NOT_FOUND)).toBe(true);
});
it('returns false when the code does not match', () => {
const error = RemoteElevaError.from({
code: ERROR_CODES.NOT_FOUND,
}, request, response);
expect(error.is(ERROR_CODES.CONFLICT)).toBe(false);
});
it('narrows details to the matching code', () => {
const err = RemoteElevaError.from({
code: ERROR_CODES.NOT_FOUND,
details: { entity: 'User' },
}, request, response);
const entity = err.is(ERROR_CODES.NOT_FOUND) ? err.details.entity : null;
expect(entity).toBe('User');
});
it('preserves request and response on the narrowed type', () => {
const err = RemoteElevaError.from({
code: ERROR_CODES.NOT_FOUND,
details: { entity: 'User' },
}, request, response);
expect(err.is(ERROR_CODES.NOT_FOUND)).toBe(true);
expect(err.request).toBe(request);
expect(err.response).toBe(response);
expect(err.details).toEqual({ entity: 'User' });
});
it('narrows correctly on a union-typed error', () => {
const err = RemoteElevaError.from(validBody, request, response);
const entity = err.is(ERROR_CODES.NOT_FOUND) ? err.details.entity : null;
expect(entity).toBe('User');
});
it('returns true when a plain string matches the code', () => {
const error = RemoteElevaError.from({ code: ERROR_CODES.NOT_FOUND }, request, response);
expect(error.is('NOT_FOUND')).toBe(true);
});
it('returns false when a plain string does not match the code', () => {
const error = RemoteElevaError.from({ code: ERROR_CODES.NOT_FOUND }, request, response);
expect(error.is('CONFLICT')).toBe(false);
});
});
describe('toJSON()', () => {
it('includes all ElevaError fields plus request and response', () => {
const error = RemoteElevaError.from({
code: ERROR_CODES.NOT_FOUND,
details: { entity: 'User' },
}, request, response);
const json = error.toJSON();
expect(json.code).toBe(ERROR_CODES.NOT_FOUND);
expect(json.kind).toBe(ErrorKind.DOMAIN);
expect(json.status).toBe(404);
expect(json.details).toEqual({ entity: 'User' });
expect(json.request).toBe(request);
expect(json.response).toBe(response);
});
it('toString serializes request and response', () => {
const error = RemoteElevaError.from({ code: ERROR_CODES.INTERNAL_SERVER_ERROR }, request, response);
const parsed = JSON.parse(error.toString());
expect(parsed.request.resource).toBe('/api/users/1');
expect(parsed.response.statusCode).toBe(404);
});
});
describe('from()', () => {
it('constructs a RemoteElevaError instance (not just ElevaError)', () => {
const error = RemoteElevaError.from(validBody, request, response);
expect(error).toBeInstanceOf(RemoteElevaError);
expect(error).toBeInstanceOf(ElevaError);
expect(error.name).toBe('RemoteElevaError');
});
it('parses the body and sets code, message and details correctly', () => {
const error = RemoteElevaError.from(validBody, request, response);
expect(error.code).toBe(ERROR_CODES.NOT_FOUND);
expect(error.message).toBe('User not found');
});
it('attaches request and response context', () => {
const error = RemoteElevaError.from(validBody, request, response);
expect(error.request).toBe(request);
expect(error.response).toBe(response);
});
it('uses the provided cause', () => {
const cause = new Error('axios error');
const error = RemoteElevaError.from(validBody, request, response, cause);
expect(error.cause).toBe(cause);
});
it('falls back to INTERNAL_SERVER_ERROR when body is malformed', () => {
const error = RemoteElevaError.from({ unexpected: true }, request, response);
expect(error.code).toBe(ERROR_CODES.INTERNAL_SERVER_ERROR);
expect(error.message).toMatch(/^Failed to parse error payload:/);
});
it('uses the raw body as cause when body is malformed and no cause is provided', () => {
const body = 'not a valid error';
const error = RemoteElevaError.from(body, request, response);
expect(error.cause).toBe(body);
});
it('prefers the provided cause over the parsed cause on malformed body', () => {
const axiosCause = new Error('axios');
const error = RemoteElevaError.from('bad body', request, response, axiosCause);
expect(error.cause).toBe(axiosCause);
});
it('toJSON() includes request and response after from()', () => {
const error = RemoteElevaError.from(validBody, request, response);
const json = error.toJSON();
expect(json.request).toBe(request);
expect(json.response).toBe(response);
});
});
describe('custom / vendor-specific error codes', () => {
it('preserves an unknown code string verbatim', () => {
const error = RemoteElevaError.from({ code: 'RATE_LIMIT', status: 429, message: 'Too many requests', kind: 'domain' }, request, response);
expect(error.code).toBe('RATE_LIMIT');
});
it('is() returns true for the custom code string', () => {
const error = RemoteElevaError.from({ code: 'RATE_LIMIT', status: 429, message: 'Too many requests', kind: 'domain' }, request, response);
expect(error.is('RATE_LIMIT')).toBe(true);
});
it('is() returns false for a different custom code string', () => {
const error = RemoteElevaError.from({ code: 'RATE_LIMIT', status: 429, message: 'Too many requests', kind: 'domain' }, request, response);
expect(error.is('QUOTA_EXCEEDED')).toBe(false);
});
it('uses status and kind from the body when both are valid', () => {
const error = RemoteElevaError.from({ code: 'RATE_LIMIT', status: 429, message: 'Too many requests', kind: 'domain' }, request, response);
expect(error.status).toBe(429);
expect(error.kind).toBe(ErrorKind.DOMAIN);
});
it('falls back to INTERNAL_SERVER_ERROR status and kind when they are absent', () => {
const error = RemoteElevaError.from({ code: 'UNKNOWN_VENDOR_CODE', message: 'Something broke' }, request, response);
expect(error.code).toBe('UNKNOWN_VENDOR_CODE');
expect(error.status).toBe(500);
expect(error.kind).toBe(ErrorKind.SYSTEM);
});
it('preserves the message from the body', () => {
const error = RemoteElevaError.from({ code: 'RATE_LIMIT', status: 429, message: 'Retry after 60 s', kind: 'domain' }, request, response);
expect(error.message).toBe('Retry after 60 s');
});
it('preserves details from the body', () => {
const error = RemoteElevaError.from({ code: 'RATE_LIMIT', status: 429, kind: 'domain', details: { retryAfter: 60 } }, request, response);
expect(error.details.retryAfter).toBe(60);
});
it('toJSON() includes the custom code, request and response', () => {
const error = RemoteElevaError.from({ code: 'RATE_LIMIT', status: 429, message: 'Too many requests', kind: 'domain' }, request, response);
const json = error.toJSON();
expect(json.code).toBe('RATE_LIMIT');
expect(json.request).toBe(request);
expect(json.response).toBe(response);
});
});
});
//# sourceMappingURL=errors_remote.spec.js.map