UNPKG

@eleva-io/erp-sdk

Version:

SDK oficial para el ERP de Eleva

247 lines 12.7 kB
import { z } from 'zod'; import { ElevaError, ERROR_CODES, ErrorKind } from '.'; const validationDetails = { errors: [{ validation: 'email', code: 'invalid_string', message: 'Invalid email', path: ['email'] }], }; const notFoundDetails = { entity: 'User' }; const conflictDetails = [{ key: 'email', value: 'already@taken.com', cause: 'duplicate' }]; describe('ElevaError', () => { describe('constructor', () => { it('is an instance of Error and ElevaError', () => { const error = ElevaError.internal({}); expect(error).toBeInstanceOf(Error); expect(error).toBeInstanceOf(ElevaError); }); it('sets name to "ElevaError"', () => { const error = ElevaError.internal({}); expect(error.name).toBe('ElevaError'); }); it('picks kind, status and default message from the catalog', () => { const error = ElevaError.internal({}); expect(error.code).toBe(ERROR_CODES.INTERNAL_SERVER_ERROR); expect(error.kind).toBe(ErrorKind.SYSTEM); expect(error.status).toBe(500); expect(error.message).toBe('Internal server error'); }); it('uses a custom message when provided', () => { const error = ElevaError.internal({ message: 'Something blew up' }); expect(error.message).toBe('Something blew up'); }); it('exposes structured details (VALIDATION)', () => { const error = ElevaError.validation(validationDetails); expect(error.details).toEqual(validationDetails); }); it('exposes structured details (NOT_FOUND)', () => { const error = ElevaError.notFound(notFoundDetails); expect(error.details).toEqual(notFoundDetails); }); it('exposes structured details (CONFLICT)', () => { const error = ElevaError.conflict({ details: conflictDetails }); expect(error.details).toEqual(conflictDetails); }); it('allows omitting details for codes with fully optional shapes', () => { const error = ElevaError.unauthorized({}); expect(error.code).toBe(ERROR_CODES.UNAUTHORIZED); expect(error.details).toEqual({}); }); it('forwards cause to the underlying Error', () => { const cause = new Error('boom'); const error = ElevaError.internal({ cause }); expect(error.cause).toBe(cause); }); it('captures a stack trace', () => { const error = ElevaError.internal({}); expect(error.stack).toMatch(/ElevaError/); }); }); describe('is()', () => { it('returns true when the code matches', () => { const error = ElevaError.notFound(notFoundDetails); expect(error.is(ERROR_CODES.NOT_FOUND)).toBe(true); }); it('returns false when the code does not match', () => { const error = ElevaError.notFound(notFoundDetails); expect(error.is(ERROR_CODES.CONFLICT)).toBe(false); }); it('narrows details to the matching code', () => { const err = ElevaError.notFound(notFoundDetails); 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 = ElevaError.notFound(notFoundDetails); expect(error.is('NOT_FOUND')).toBe(true); }); it('returns false when a plain string does not match the code', () => { const error = ElevaError.notFound(notFoundDetails); expect(error.is('CONFLICT')).toBe(false); }); it('returns true for uncatalogued codes matched by string', () => { const error = ElevaError.parse({ code: 'CUSTOM_CODE', message: 'custom' }); expect(error.is('CUSTOM_CODE')).toBe(true); }); }); describe('toJSON() / toString()', () => { it('toJSON returns a plain object with all fields', () => { const error = ElevaError.validation(validationDetails); expect(error.toJSON()).toEqual({ code: ERROR_CODES.VALIDATION, kind: ErrorKind.DOMAIN, status: 400, message: 'Validation error', details: validationDetails, }); }); it('toString returns a JSON string equivalent to toJSON()', () => { const error = ElevaError.validation(validationDetails); expect(error.toString()).toBe(JSON.stringify(error.toJSON())); }); }); describe('parse()', () => { it('reconstructs an ElevaError from a valid response body', () => { const body = { code: ERROR_CODES.VALIDATION, message: 'Validation error', details: validationDetails, }; const error = ElevaError.parse(body); expect(error).toBeInstanceOf(ElevaError); expect(error.code).toBe(ERROR_CODES.VALIDATION); expect(error.message).toBe('Validation error'); expect(error.details).toEqual(validationDetails); }); it('derives kind and status from the catalog when not provided', () => { const error = ElevaError.parse({ code: ERROR_CODES.NOT_FOUND, message: 'Not found' }); expect(error.kind).toBe(ErrorKind.DOMAIN); expect(error.status).toBe(404); }); it('uses kind and status from the payload when provided', () => { const body = { code: ERROR_CODES.NOT_FOUND, kind: ErrorKind.SYSTEM, status: 503, message: 'Override' }; const error = ElevaError.parse(body); expect(error.kind).toBe(ErrorKind.SYSTEM); expect(error.status).toBe(503); }); it('falls back to catalog message when message is not provided', () => { const error = ElevaError.parse({ code: ERROR_CODES.NOT_FOUND }); expect(error.message).toBe('Not found'); }); it('preserves uncatalogued codes and their message as-is', () => { const body = { code: 'SOME_NEW_CODE', message: 'Something went wrong on the server' }; const error = ElevaError.parse(body); expect(error.code).toBe('SOME_NEW_CODE'); expect(error.message).toBe('Something went wrong on the server'); }); it('falls back to INTERNAL_SERVER_ERROR kind and status for uncatalogued codes', () => { const body = { code: 'SOME_NEW_CODE', message: 'New error type' }; const error = ElevaError.parse(body); expect(error.kind).toBe(ErrorKind.SYSTEM); expect(error.status).toBe(500); }); it('falls back to INTERNAL_SERVER_ERROR on malformed payloads', () => { const body = 'not an object'; const error = ElevaError.parse(body); expect(error.code).toBe(ERROR_CODES.INTERNAL_SERVER_ERROR); expect(error.message).toMatch(/^Failed to parse error payload:/); expect(error.cause).toBe(body); }); }); describe('fromZodError()', () => { const fail = (schema, value) => { const result = schema.safeParse(value); return result.success ? new z.ZodError([]) : result.error; }; it('converts a ZodError into a VALIDATION ElevaError', () => { const schema = z.object({ email: z.string().email(), age: z.number().int() }); const error = ElevaError.fromZodError(fail(schema, { email: 'not-an-email', age: 1.5 })); expect(error.code).toBe(ERROR_CODES.VALIDATION); expect(error.details.errors.length).toBeGreaterThan(0); const first = error.details.errors[0]; expect(first.path).toEqual(expect.any(Array)); expect(typeof first.message).toBe('string'); }); it('uses "root" as path label when issue path is empty', () => { const error = ElevaError.fromZodError(fail(z.string(), 123)); expect(error.details.errors[0].validation).toBe('root'); }); }); describe('factories', () => { it('validation() accepts an errors array', () => { const error = ElevaError.validation({ errors: [ { validation: 'x', code: 'c', message: 'm', path: ['x'] }, { validation: 'y', code: 'c', message: 'm', path: ['y'] }, ], }); expect(error.code).toBe(ERROR_CODES.VALIDATION); expect(error.details.errors).toHaveLength(2); }); it('invalidField() builds a single-field validation error', () => { const error = ElevaError.invalidField({ error: { validation: 'email', code: 'invalid_string', message: 'Invalid email', path: ['email'] }, }); expect(error.code).toBe(ERROR_CODES.VALIDATION); expect(error.details.errors[0]).toEqual({ validation: 'email', code: 'invalid_string', message: 'Invalid email', path: ['email'], }); }); it('notFound() builds a NOT_FOUND error with entity', () => { const error = ElevaError.notFound({ entity: 'User' }); expect(error.code).toBe(ERROR_CODES.NOT_FOUND); expect(error.details).toEqual({ entity: 'User' }); }); it('conflict() builds a CONFLICT error', () => { const error = ElevaError.conflict({ details: conflictDetails }); expect(error.code).toBe(ERROR_CODES.CONFLICT); expect(error.details).toEqual(conflictDetails); }); it('simple factories use catalog defaults with no args', () => { expect(ElevaError.badRequest().code).toBe(ERROR_CODES.BAD_REQUEST); expect(ElevaError.authentication().code).toBe(ERROR_CODES.AUTHENTICATION); expect(ElevaError.unauthorized().code).toBe(ERROR_CODES.UNAUTHORIZED); expect(ElevaError.notAcceptable().code).toBe(ERROR_CODES.NOT_ACCEPTABLE); expect(ElevaError.gone().code).toBe(ERROR_CODES.GONE); expect(ElevaError.unprocessableEntity().code).toBe(ERROR_CODES.UNPROCESSABLE_ENTITY); expect(ElevaError.serviceUnavailable().code).toBe(ERROR_CODES.SERVICE_UNAVAILABLE); }); it('unprocessableEntity() carries optional structured details', () => { const error = ElevaError.unprocessableEntity({ details: { field: 'email', reason: 'already_taken' } }); expect(error.code).toBe(ERROR_CODES.UNPROCESSABLE_ENTITY); expect(error.status).toBe(422); expect(error.details).toEqual({ field: 'email', reason: 'already_taken' }); }); it('simple factories accept message and details', () => { const error = ElevaError.unauthorized({ message: 'Forbidden', details: { requiredScope: 'admin', resource: 'users' }, }); expect(error.message).toBe('Forbidden'); expect(error.details).toEqual({ requiredScope: 'admin', resource: 'users' }); }); it('badRequest() carries optional structured details', () => { const error = ElevaError.badRequest({ details: { field: 'id', reason: 'required' } }); expect(error.code).toBe(ERROR_CODES.BAD_REQUEST); expect(error.details).toEqual({ field: 'id', reason: 'required' }); }); it('mandatoryField() builds a MANDATORY_FIELD error with field', () => { const error = ElevaError.mandatoryField({ field: 'agentId' }); expect(error.code).toBe(ERROR_CODES.MANDATORY_FIELD); expect(error.status).toBe(400); expect(error.details).toEqual({ field: 'agentId' }); }); it('mandatoryField() accepts a custom message', () => { const error = ElevaError.mandatoryField({ field: 'permissions', message: 'permissions is required' }); expect(error.message).toBe('permissions is required'); expect(error.details).toEqual({ field: 'permissions' }); }); it('internal() propagates the cause', () => { const cause = new Error('db down'); const error = ElevaError.internal({ cause }); expect(error.code).toBe(ERROR_CODES.INTERNAL_SERVER_ERROR); expect(error.cause).toBe(cause); }); }); }); //# sourceMappingURL=errors.spec.js.map