import { describe, test, expect } from 'bun:test'; import { ShadeError, InvalidSignatureError, DecryptionError, NoSessionError, MaxSkipExceededError, DuplicateMessageError, UntrustedIdentityError, PreKeyNotFoundError, ReplayError, IdentityRotationError, NetworkError, StorageError, ValidationError, TimeoutError, RateLimitError, ConfigurationError, UnauthorizedError, errorToHttpStatus, } from '../src/errors.js'; describe('Error hierarchy', () => { test('all errors extend ShadeError', () => { const errors = [ new InvalidSignatureError(), new DecryptionError(), new NoSessionError('addr'), new MaxSkipExceededError(1001, 1000), new DuplicateMessageError(), new UntrustedIdentityError('addr'), new PreKeyNotFoundError(1, 'signed'), new ReplayError(), new IdentityRotationError(), new NetworkError('net'), new StorageError('storage'), new ValidationError('val'), new TimeoutError(), new RateLimitError(), new ConfigurationError('cfg'), new UnauthorizedError(), ]; for (const e of errors) { expect(e).toBeInstanceOf(ShadeError); expect(e).toBeInstanceOf(Error); expect(e.code).toMatch(/^SHADE_/); expect(e.name).not.toBe('ShadeError'); } }); test('toJSON produces serializable form', () => { const e = new InvalidSignatureError('bad sig'); const json = e.toJSON(); expect(json.name).toBe('InvalidSignatureError'); expect(json.code).toBe('SHADE_INVALID_SIGNATURE'); expect(json.message).toBe('bad sig'); const str = JSON.stringify(e); const parsed = JSON.parse(str); expect(parsed.code).toBe('SHADE_INVALID_SIGNATURE'); }); test('NetworkError carries status code', () => { const e = new NetworkError('server error', 503); expect(e.statusCode).toBe(503); }); test('RateLimitError carries retry-after', () => { const e = new RateLimitError('too many', 60); expect(e.retryAfterSeconds).toBe(60); }); test('ValidationError carries field name', () => { const e = new ValidationError('must be positive', 'count'); expect(e.field).toBe('count'); }); }); describe('errorToHttpStatus', () => { test('unknown error → 500', () => { expect(errorToHttpStatus(new Error('plain'))).toBe(500); expect(errorToHttpStatus('string error')).toBe(500); expect(errorToHttpStatus(null)).toBe(500); }); test('auth errors → 401/403', () => { expect(errorToHttpStatus(new InvalidSignatureError())).toBe(401); expect(errorToHttpStatus(new UnauthorizedError())).toBe(401); expect(errorToHttpStatus(new UntrustedIdentityError('x'))).toBe(403); }); test('not-found errors → 404', () => { expect(errorToHttpStatus(new NoSessionError('x'))).toBe(404); expect(errorToHttpStatus(new PreKeyNotFoundError(1, 'signed'))).toBe(404); }); test('validation → 400', () => { expect(errorToHttpStatus(new ValidationError('bad'))).toBe(400); }); test('replay/duplicate → 409', () => { expect(errorToHttpStatus(new ReplayError())).toBe(409); expect(errorToHttpStatus(new DuplicateMessageError())).toBe(409); }); test('rate limit → 429', () => { expect(errorToHttpStatus(new RateLimitError())).toBe(429); }); test('infra errors → 503', () => { expect(errorToHttpStatus(new NetworkError('x'))).toBe(503); expect(errorToHttpStatus(new StorageError('x'))).toBe(503); expect(errorToHttpStatus(new ConfigurationError('x'))).toBe(503); }); test('timeout → 504', () => { expect(errorToHttpStatus(new TimeoutError())).toBe(504); }); });