| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- /**
- * @fileoverview Tests for error handling system
- * @author GrabZilla Development Team
- * @version 2.1.0
- */
- import { describe, it, expect, beforeEach, vi } from 'vitest'
- import { JSDOM } from 'jsdom'
- // Mock Electron API
- const mockElectronAPI = {
- showNotification: vi.fn(),
- showErrorDialog: vi.fn(),
- showInfoDialog: vi.fn()
- }
- describe('Error Handling System', () => {
- let dom
- let window
- let document
- let ErrorHandler
- let errorHandler
- let ERROR_TYPES
- beforeEach(() => {
- // Set up DOM environment
- dom = new JSDOM(`
- <!DOCTYPE html>
- <html>
- <body>
- <div id="app"></div>
- </body>
- </html>
- `, {
- url: 'http://localhost',
- pretendToBeVisual: true,
- resources: 'usable'
- })
- window = dom.window
- document = window.document
- global.window = window
- global.document = document
- // Mock APIs
- window.electronAPI = mockElectronAPI
- // Load the error handler
- const fs = require('fs')
- const path = require('path')
- const errorHandlerScript = fs.readFileSync(
- path.join(__dirname, '../scripts/utils/error-handler.js'),
- 'utf8'
- )
-
- // Execute the script in the window context
- const script = new window.Function(errorHandlerScript)
- script.call(window)
- ErrorHandler = window.ErrorHandler
- errorHandler = window.errorHandler
- ERROR_TYPES = window.ERROR_TYPES
- })
- describe('ErrorHandler', () => {
- it('should initialize with correct default values', () => {
- const handler = new ErrorHandler()
-
- expect(handler.errorHistory).toEqual([])
- expect(handler.maxHistorySize).toBe(50)
- expect(handler.notificationQueue).toEqual([])
- })
- it('should parse network errors correctly', async () => {
- const error = new Error('Network connection failed')
- const errorInfo = await errorHandler.handleError(error)
-
- expect(errorInfo.type).toBe(ERROR_TYPES.NETWORK)
- expect(errorInfo.message).toBe('Network connection error')
- expect(errorInfo.recoverable).toBe(true)
- })
- it('should parse binary missing errors correctly', async () => {
- const error = new Error('yt-dlp binary not found')
- const errorInfo = await errorHandler.handleError(error)
-
- expect(errorInfo.type).toBe(ERROR_TYPES.BINARY_MISSING)
- expect(errorInfo.message).toBe('Required application components are missing')
- expect(errorInfo.recoverable).toBe(false)
- })
- it('should parse permission errors correctly', async () => {
- const error = new Error('Permission denied - not writable')
- const errorInfo = await errorHandler.handleError(error)
-
- expect(errorInfo.type).toBe(ERROR_TYPES.PERMISSION)
- expect(errorInfo.message).toBe('Permission denied')
- expect(errorInfo.recoverable).toBe(true)
- })
- it('should parse video unavailable errors correctly', async () => {
- const error = new Error('Video is unavailable or private')
- const errorInfo = await errorHandler.handleError(error)
-
- expect(errorInfo.type).toBe(ERROR_TYPES.VIDEO_UNAVAILABLE)
- expect(errorInfo.message).toBe('Video is unavailable or has been removed')
- expect(errorInfo.recoverable).toBe(false)
- })
- it('should parse age-restricted errors correctly', async () => {
- const error = new Error('Sign in to confirm your age')
- const errorInfo = await errorHandler.handleError(error)
-
- expect(errorInfo.type).toBe(ERROR_TYPES.AGE_RESTRICTED)
- expect(errorInfo.message).toBe('Age-restricted content requires authentication')
- expect(errorInfo.recoverable).toBe(true)
- })
- it('should parse disk space errors correctly', async () => {
- const error = new Error('No space left on device')
- const errorInfo = await errorHandler.handleError(error)
-
- expect(errorInfo.type).toBe(ERROR_TYPES.DISK_SPACE)
- expect(errorInfo.message).toBe('Insufficient disk space')
- expect(errorInfo.recoverable).toBe(true)
- })
- it('should parse format errors correctly', async () => {
- const error = new Error('Requested format not available')
- const errorInfo = await errorHandler.handleError(error)
-
- expect(errorInfo.type).toBe(ERROR_TYPES.FORMAT_ERROR)
- expect(errorInfo.message).toBe('Requested video quality or format not available')
- expect(errorInfo.recoverable).toBe(true)
- })
- it('should add errors to history', async () => {
- const error = new Error('Test error')
- await errorHandler.handleError(error)
-
- expect(errorHandler.errorHistory).toHaveLength(1)
- expect(errorHandler.errorHistory[0].originalError).toBe(error)
- })
- it('should limit error history size', async () => {
- const handler = new ErrorHandler()
- handler.maxHistorySize = 3
-
- // Add more errors than the limit
- for (let i = 0; i < 5; i++) {
- await handler.handleError(new Error(`Error ${i}`))
- }
-
- expect(handler.errorHistory).toHaveLength(3)
- })
- it('should generate unique error IDs', () => {
- const id1 = errorHandler.generateErrorId()
- const id2 = errorHandler.generateErrorId()
-
- expect(id1).not.toBe(id2)
- expect(id1).toMatch(/^error_\d+_[a-z0-9]+$/)
- })
- it('should show error notifications', async () => {
- mockElectronAPI.showNotification.mockResolvedValue({ success: true })
-
- const error = new Error('Test error')
- await errorHandler.handleError(error, {}, { showNotification: true })
-
- expect(mockElectronAPI.showNotification).toHaveBeenCalled()
- })
- it('should show error dialogs for critical errors', async () => {
- mockElectronAPI.showErrorDialog.mockResolvedValue({ success: true, response: 0 })
-
- const error = new Error('Critical error')
- await errorHandler.handleError(error, {}, { showDialog: true })
-
- expect(mockElectronAPI.showErrorDialog).toHaveBeenCalled()
- })
- it('should dispatch in-app error events', async () => {
- let eventFired = false
- let eventDetail = null
-
- document.addEventListener('app-error', (event) => {
- eventFired = true
- eventDetail = event.detail
- })
-
- const error = new Error('Test error')
- await errorHandler.handleError(error, {}, { showInUI: true })
-
- expect(eventFired).toBe(true)
- expect(eventDetail).toHaveProperty('message')
- expect(eventDetail).toHaveProperty('type')
- })
- it('should handle binary errors specifically', async () => {
- mockElectronAPI.showNotification.mockResolvedValue({ success: true })
- mockElectronAPI.showErrorDialog.mockResolvedValue({ success: true, response: 0 })
-
- const errorInfo = await errorHandler.handleBinaryError('yt-dlp')
-
- expect(errorInfo.type).toBe(ERROR_TYPES.BINARY_MISSING)
- expect(errorInfo.message).toContain('yt-dlp')
- expect(mockElectronAPI.showNotification).toHaveBeenCalled()
- expect(mockElectronAPI.showErrorDialog).toHaveBeenCalled()
- })
- it('should handle network errors with retry logic', async () => {
- mockElectronAPI.showErrorDialog.mockResolvedValue({ success: true, response: 0 })
-
- const retryCallback = vi.fn().mockResolvedValue('success')
- const error = new Error('Network timeout')
-
- const result = await errorHandler.handleNetworkError(error, retryCallback, 1)
-
- expect(retryCallback).toHaveBeenCalled()
- })
- it('should provide error statistics', () => {
- // Add some test errors
- errorHandler.errorHistory = [
- { type: ERROR_TYPES.NETWORK, recoverable: true, timestamp: new Date() },
- { type: ERROR_TYPES.BINARY_MISSING, recoverable: false, timestamp: new Date() },
- { type: ERROR_TYPES.NETWORK, recoverable: true, timestamp: new Date() }
- ]
-
- const stats = errorHandler.getStats()
-
- expect(stats.total).toBe(3)
- expect(stats.byType.network).toBe(2)
- expect(stats.byType.binary_missing).toBe(1)
- expect(stats.recoverable).toBe(2)
- })
- it('should clear error history', () => {
- errorHandler.errorHistory = [{ test: 'error' }]
- errorHandler.clearHistory()
-
- expect(errorHandler.errorHistory).toEqual([])
- })
- it('should check if errors are recoverable', () => {
- const recoverableError = { recoverable: true }
- const nonRecoverableError = { recoverable: false }
-
- expect(errorHandler.isRecoverable(recoverableError)).toBe(true)
- expect(errorHandler.isRecoverable(nonRecoverableError)).toBe(false)
- })
- })
- describe('Error Types', () => {
- it('should have all required error types', () => {
- expect(ERROR_TYPES).toHaveProperty('NETWORK')
- expect(ERROR_TYPES).toHaveProperty('BINARY_MISSING')
- expect(ERROR_TYPES).toHaveProperty('PERMISSION')
- expect(ERROR_TYPES).toHaveProperty('VIDEO_UNAVAILABLE')
- expect(ERROR_TYPES).toHaveProperty('AGE_RESTRICTED')
- expect(ERROR_TYPES).toHaveProperty('DISK_SPACE')
- expect(ERROR_TYPES).toHaveProperty('FORMAT_ERROR')
- expect(ERROR_TYPES).toHaveProperty('UNKNOWN')
- })
- it('should have correct configuration for each type', () => {
- expect(ERROR_TYPES.NETWORK.recoverable).toBe(true)
- expect(ERROR_TYPES.BINARY_MISSING.recoverable).toBe(false)
- expect(ERROR_TYPES.PERMISSION.recoverable).toBe(true)
- expect(ERROR_TYPES.VIDEO_UNAVAILABLE.recoverable).toBe(false)
- })
- })
- })
|