import { describe, expect, it } from 'vitest' import { ErrorExtractorId, type ErrorInfo, extractErrorMessage } from '@/tools/error-extractors' describe('Error Extractors', () => { describe('extractErrorMessage', () => { it('should extract GraphQL error messages', () => { const errorInfo: ErrorInfo = { status: 400, data: { errors: [{ message: 'GraphQL validation error' }], }, } expect(extractErrorMessage(errorInfo)).toBe('GraphQL validation error') }) it('should extract Twitter API error details', () => { const errorInfo: ErrorInfo = { status: 403, data: { errors: [{ detail: 'Rate limit exceeded' }], }, } expect(extractErrorMessage(errorInfo)).toBe('Rate limit exceeded') }) it('should extract Telegram API description', () => { const errorInfo: ErrorInfo = { status: 403, data: { ok: false, error_code: 403, description: "Forbidden: bots can't send messages to bots", }, } expect(extractErrorMessage(errorInfo)).toBe("Forbidden: bots can't send messages to bots") }) it('should extract standard message field', () => { const errorInfo: ErrorInfo = { status: 400, data: { message: 'Invalid request parameters', }, } expect(extractErrorMessage(errorInfo)).toBe('Invalid request parameters') }) it('should extract OAuth error_description', () => { const errorInfo: ErrorInfo = { status: 401, data: { error: 'invalid_grant', error_description: 'The provided authorization grant is invalid', }, } expect(extractErrorMessage(errorInfo)).toBe('The provided authorization grant is invalid') }) it('should extract SOAP fault strings', () => { const errorInfo: ErrorInfo = { status: 500, data: { fault: { faultstring: 'SOAP processing error', }, }, } expect(extractErrorMessage(errorInfo)).toBe('SOAP processing error') }) it('should extract nested error object messages', () => { const errorInfo: ErrorInfo = { status: 400, data: { error: { message: 'Resource not found', }, }, } expect(extractErrorMessage(errorInfo)).toBe('Resource not found') }) it('should handle string error field', () => { const errorInfo: ErrorInfo = { status: 400, data: { error: 'Bad request', }, } expect(extractErrorMessage(errorInfo)).toBe('Bad request') }) it('should extract errors array with strings', () => { const errorInfo: ErrorInfo = { status: 400, data: { errors: ['Email is required', 'Password is too short'], }, } expect(extractErrorMessage(errorInfo)).toBe('Email is required') }) it('should extract plain text error responses', () => { const errorInfo: ErrorInfo = { status: 401, statusText: 'Unauthorized', data: 'Invalid access token', } expect(extractErrorMessage(errorInfo)).toBe('Invalid access token') }) it('should extract plain text error from APIs like Apollo', () => { const errorInfo: ErrorInfo = { status: 403, statusText: 'Forbidden', data: 'Invalid API key provided', } expect(extractErrorMessage(errorInfo)).toBe('Invalid API key provided') }) it('should trim whitespace from plain text errors', () => { const errorInfo: ErrorInfo = { status: 400, statusText: 'Bad Request', data: ' Error message with spaces ', } expect(extractErrorMessage(errorInfo)).toBe('Error message with spaces') }) it('should skip empty plain text and use HTTP status text fallback', () => { const errorInfo: ErrorInfo = { status: 500, statusText: 'Internal Server Error', data: ' ', } expect(extractErrorMessage(errorInfo)).toBe('Internal Server Error') }) it('should use HTTP status text as fallback', () => { const errorInfo: ErrorInfo = { status: 404, statusText: 'Not Found', data: {}, } expect(extractErrorMessage(errorInfo)).toBe('Not Found') }) it('should use final fallback when no pattern matches', () => { const errorInfo: ErrorInfo = { status: 500, data: {}, } expect(extractErrorMessage(errorInfo)).toBe('Request failed with status 500') }) it('should handle undefined errorInfo', () => { expect(extractErrorMessage(undefined)).toBe('Request failed with status unknown') }) it('should handle empty strings gracefully', () => { const errorInfo: ErrorInfo = { status: 400, data: { message: '', }, } expect(extractErrorMessage(errorInfo)).toBe('Request failed with status 400') }) }) describe('extractErrorMessage with explicit extractorId', () => { it('should use specified extractor directly (deterministic)', () => { const errorInfo: ErrorInfo = { status: 403, data: { description: "Forbidden: bots can't send messages to bots", message: 'Some other message', }, } expect(extractErrorMessage(errorInfo, ErrorExtractorId.TELEGRAM_DESCRIPTION)).toBe( "Forbidden: bots can't send messages to bots" ) }) it('should use specified extractor even when other patterns match first', () => { const errorInfo: ErrorInfo = { status: 400, data: { errors: [{ message: 'GraphQL error' }], message: 'Standard message', }, } expect(extractErrorMessage(errorInfo, ErrorExtractorId.STANDARD_MESSAGE)).toBe( 'Standard message' ) }) it('should fallback when specified extractor does not find message', () => { const errorInfo: ErrorInfo = { status: 404, data: { someOtherField: 'value', }, } expect(extractErrorMessage(errorInfo, ErrorExtractorId.TELEGRAM_DESCRIPTION)).toBe( 'Request failed with status 404' ) }) it('should warn and fallback for non-existent extractor ID', () => { const errorInfo: ErrorInfo = { status: 500, data: { message: 'Error message', }, } expect(extractErrorMessage(errorInfo, 'non-existent-extractor')).toBe( 'Request failed with status 500' ) }) it('should use plain text extractor when explicitly specified', () => { const errorInfo: ErrorInfo = { status: 403, statusText: 'Forbidden', data: 'Plain text error message', } expect(extractErrorMessage(errorInfo, ErrorExtractorId.PLAIN_TEXT_DATA)).toBe( 'Plain text error message' ) }) }) })