sec-edgar-toolkit
Version:
Open source toolkit to facilitate working with the SEC EDGAR database
208 lines • 10.7 kB
JavaScript
;
/**
* Tests for comprehensive error handling
*/
Object.defineProperty(exports, "__esModule", { value: true });
const exceptions_1 = require("../exceptions");
describe('Error Classes', () => {
describe('InvalidUserAgentError', () => {
it('should create error for missing user agent', () => {
const error = new exceptions_1.InvalidUserAgentError();
expect(error.message).toContain('User agent is required');
expect(error.field).toBe('userAgent');
expect(error.name).toBe('InvalidUserAgentError');
});
it('should create error for invalid user agent', () => {
const error = new exceptions_1.InvalidUserAgentError('Bot');
expect(error.message).toContain('Invalid user agent format');
expect(error.message).toContain('Bot');
});
});
describe('CompanyNotFoundError', () => {
it('should include identifier and search type', () => {
const error = new exceptions_1.CompanyNotFoundError('AAPL', 'ticker');
expect(error.message).toBe('Company not found: AAPL (searched by ticker)');
expect(error.identifier).toBe('AAPL');
expect(error.searchType).toBe('ticker');
expect(error.statusCode).toBe(404);
});
it('should handle CIK search', () => {
const error = new exceptions_1.CompanyNotFoundError('0000320193', 'cik');
expect(error.message).toContain('0000320193');
expect(error.searchType).toBe('cik');
});
});
describe('InvalidFormTypeError', () => {
it('should show invalid form type', () => {
const error = new exceptions_1.InvalidFormTypeError('INVALID-FORM');
expect(error.message).toBe('Invalid form type: "INVALID-FORM"');
});
it('should include valid types when provided', () => {
const validTypes = ['10-K', '10-Q', '8-K'];
const error = new exceptions_1.InvalidFormTypeError('INVALID', validTypes);
expect(error.message).toContain('Valid types: 10-K, 10-Q, 8-K');
});
});
describe('Network Errors', () => {
it('should create TimeoutError', () => {
const error = new exceptions_1.TimeoutError('https://api.example.com', 30000);
expect(error.message).toBe('Request timed out after 30000ms');
expect(error.url).toBe('https://api.example.com');
expect(error.name).toBe('TimeoutError');
});
it('should create NetworkError', () => {
const originalError = new Error('ECONNREFUSED');
const error = new exceptions_1.NetworkError('Connection refused', 'https://api.example.com', originalError);
expect(error.message).toBe('Connection refused');
expect(error.url).toBe('https://api.example.com');
expect(error.originalError).toBe(originalError);
});
it('should create RequestError', () => {
const error = new exceptions_1.RequestError('Bad Request', 'https://api.example.com', 400, { error: 'Invalid parameters' });
expect(error.statusCode).toBe(400);
expect(error.response).toEqual({ error: 'Invalid parameters' });
});
});
describe('Filing Errors', () => {
it('should create FilingNotFoundError', () => {
const error = new exceptions_1.FilingNotFoundError('0000320193-24-000001', '0000320193');
expect(error.message).toBe('Filing not found: 0000320193-24-000001 for CIK 0000320193');
expect(error.accessionNumber).toBe('0000320193-24-000001');
expect(error.cik).toBe('0000320193');
expect(error.statusCode).toBe(404);
});
});
describe('Parsing Errors', () => {
it('should create XMLParsingError with location', () => {
const error = new exceptions_1.XMLParsingError('Invalid character', 10, 25);
expect(error.message).toBe('XML parsing error at line 10, column 25: Invalid character');
expect(error.lineNumber).toBe(10);
expect(error.columnNumber).toBe(25);
});
it('should create XMLParsingError without location', () => {
const error = new exceptions_1.XMLParsingError('Invalid XML structure');
expect(error.message).toBe('XML parsing error: Invalid XML structure');
});
});
describe('XBRL Errors', () => {
it('should create ConceptNotFoundError', () => {
const error = new exceptions_1.ConceptNotFoundError('CustomConcept', 'custom-taxonomy');
expect(error.message).toBe('XBRL concept not found: CustomConcept in custom-taxonomy');
expect(error.concept).toBe('CustomConcept');
expect(error.taxonomy).toBe('custom-taxonomy');
});
});
});
describe('ErrorHandler', () => {
describe('wrapError', () => {
it('should wrap error with context', () => {
const originalError = new Error('Database error');
const wrapped = exceptions_1.ErrorHandler.wrapError(originalError, {
operation: 'fetchFilings',
cik: '0000320193',
formType: '10-K'
}, 'Failed to fetch filings');
expect(wrapped).toBeInstanceOf(exceptions_1.ErrorWithContext);
expect(wrapped.message).toBe('Failed to fetch filings');
expect(wrapped.originalError).toBe(originalError);
expect(wrapped.context.operation).toBe('fetchFilings');
expect(wrapped.context.cik).toBe('0000320193');
});
it('should use original message if not provided', () => {
const originalError = new Error('Original message');
const wrapped = exceptions_1.ErrorHandler.wrapError(originalError, {});
expect(wrapped.message).toBe('Original message');
});
});
describe('normalize', () => {
it('should return SecEdgarApiError unchanged', () => {
const error = new exceptions_1.SecEdgarApiError('Test error');
const normalized = exceptions_1.ErrorHandler.normalize(error);
expect(normalized).toBe(error);
});
it('should wrap Error in SecEdgarApiError', () => {
const error = new Error('Regular error');
const normalized = exceptions_1.ErrorHandler.normalize(error);
expect(normalized).toBeInstanceOf(exceptions_1.SecEdgarApiError);
expect(normalized.message).toBe('Regular error');
});
it('should handle string errors', () => {
const normalized = exceptions_1.ErrorHandler.normalize('String error');
expect(normalized).toBeInstanceOf(exceptions_1.SecEdgarApiError);
expect(normalized.message).toBe('String error');
});
it('should handle unknown errors', () => {
const normalized = exceptions_1.ErrorHandler.normalize({ weird: 'object' });
expect(normalized).toBeInstanceOf(exceptions_1.SecEdgarApiError);
expect(normalized.message).toBe('An unknown error occurred');
});
});
describe('isRetryable', () => {
it('should identify rate limit errors as retryable', () => {
const error = new exceptions_1.RateLimitError();
expect(exceptions_1.ErrorHandler.isRetryable(error)).toBe(true);
});
it('should identify timeout errors as retryable', () => {
const error = new exceptions_1.TimeoutError('url', 30000);
expect(exceptions_1.ErrorHandler.isRetryable(error)).toBe(true);
});
it('should identify network errors as retryable', () => {
const error = new exceptions_1.NetworkError('Network error', 'url');
expect(exceptions_1.ErrorHandler.isRetryable(error)).toBe(true);
});
it('should identify 5xx errors as retryable', () => {
const error = new exceptions_1.RequestError('Server error', 'url', 503);
expect(exceptions_1.ErrorHandler.isRetryable(error)).toBe(true);
});
it('should identify 408 as retryable', () => {
const error = new exceptions_1.RequestError('Request timeout', 'url', 408);
expect(exceptions_1.ErrorHandler.isRetryable(error)).toBe(true);
});
it('should not retry 4xx errors', () => {
const error = new exceptions_1.RequestError('Bad request', 'url', 400);
expect(exceptions_1.ErrorHandler.isRetryable(error)).toBe(false);
});
it('should not retry non-network errors', () => {
const error = new exceptions_1.InvalidFormTypeError('10-X');
expect(exceptions_1.ErrorHandler.isRetryable(error)).toBe(false);
});
});
describe('getRetryDelay', () => {
it('should use longer delay for rate limits', () => {
const error = new exceptions_1.RateLimitError();
expect(exceptions_1.ErrorHandler.getRetryDelay(error, 1)).toBe(10000); // 2^1 * 5000
expect(exceptions_1.ErrorHandler.getRetryDelay(error, 2)).toBe(20000); // 2^2 * 5000
expect(exceptions_1.ErrorHandler.getRetryDelay(error, 3)).toBe(40000); // 2^3 * 5000
});
it('should cap rate limit delay at 60 seconds', () => {
const error = new exceptions_1.RateLimitError();
expect(exceptions_1.ErrorHandler.getRetryDelay(error, 10)).toBe(60000);
});
it('should use exponential backoff for other errors', () => {
const error = new exceptions_1.TimeoutError('url', 30000);
expect(exceptions_1.ErrorHandler.getRetryDelay(error, 1)).toBe(2000);
expect(exceptions_1.ErrorHandler.getRetryDelay(error, 2)).toBe(4000);
expect(exceptions_1.ErrorHandler.getRetryDelay(error, 3)).toBe(8000);
});
it('should cap other delays at 30 seconds', () => {
const error = new exceptions_1.NetworkError('error', 'url');
expect(exceptions_1.ErrorHandler.getRetryDelay(error, 10)).toBe(30000);
});
});
});
describe('ErrorWithContext', () => {
it('should format toString with context', () => {
const error = new exceptions_1.ErrorWithContext('Test error', {
operation: 'test',
url: 'https://example.com',
attempt: 3
});
const str = error.toString();
expect(str).toContain('ErrorWithContext');
expect(str).toContain('Test error');
expect(str).toContain('operation=test');
expect(str).toContain('url=https://example.com');
expect(str).toContain('attempt=3');
});
});
//# sourceMappingURL=error-handling.test.js.map