error-handling.test.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. /**
  2. * @fileoverview Tests for error handling system
  3. * @author GrabZilla Development Team
  4. * @version 2.1.0
  5. */
  6. import { describe, it, expect, beforeEach, vi } from 'vitest'
  7. import { JSDOM } from 'jsdom'
  8. // Mock Electron API
  9. const mockElectronAPI = {
  10. showNotification: vi.fn(),
  11. showErrorDialog: vi.fn(),
  12. showInfoDialog: vi.fn()
  13. }
  14. describe('Error Handling System', () => {
  15. let dom
  16. let window
  17. let document
  18. let ErrorHandler
  19. let errorHandler
  20. let ERROR_TYPES
  21. beforeEach(() => {
  22. // Set up DOM environment
  23. dom = new JSDOM(`
  24. <!DOCTYPE html>
  25. <html>
  26. <body>
  27. <div id="app"></div>
  28. </body>
  29. </html>
  30. `, {
  31. url: 'http://localhost',
  32. pretendToBeVisual: true,
  33. resources: 'usable'
  34. })
  35. window = dom.window
  36. document = window.document
  37. global.window = window
  38. global.document = document
  39. // Mock APIs
  40. window.electronAPI = mockElectronAPI
  41. // Load the error handler
  42. const fs = require('fs')
  43. const path = require('path')
  44. const errorHandlerScript = fs.readFileSync(
  45. path.join(__dirname, '../scripts/utils/error-handler.js'),
  46. 'utf8'
  47. )
  48. // Execute the script in the window context
  49. const script = new window.Function(errorHandlerScript)
  50. script.call(window)
  51. ErrorHandler = window.ErrorHandler
  52. errorHandler = window.errorHandler
  53. ERROR_TYPES = window.ERROR_TYPES
  54. })
  55. describe('ErrorHandler', () => {
  56. it('should initialize with correct default values', () => {
  57. const handler = new ErrorHandler()
  58. expect(handler.errorHistory).toEqual([])
  59. expect(handler.maxHistorySize).toBe(50)
  60. expect(handler.notificationQueue).toEqual([])
  61. })
  62. it('should parse network errors correctly', async () => {
  63. const error = new Error('Network connection failed')
  64. const errorInfo = await errorHandler.handleError(error)
  65. expect(errorInfo.type).toBe(ERROR_TYPES.NETWORK)
  66. expect(errorInfo.message).toBe('Network connection error')
  67. expect(errorInfo.recoverable).toBe(true)
  68. })
  69. it('should parse binary missing errors correctly', async () => {
  70. const error = new Error('yt-dlp binary not found')
  71. const errorInfo = await errorHandler.handleError(error)
  72. expect(errorInfo.type).toBe(ERROR_TYPES.BINARY_MISSING)
  73. expect(errorInfo.message).toBe('Required application components are missing')
  74. expect(errorInfo.recoverable).toBe(false)
  75. })
  76. it('should parse permission errors correctly', async () => {
  77. const error = new Error('Permission denied - not writable')
  78. const errorInfo = await errorHandler.handleError(error)
  79. expect(errorInfo.type).toBe(ERROR_TYPES.PERMISSION)
  80. expect(errorInfo.message).toBe('Permission denied')
  81. expect(errorInfo.recoverable).toBe(true)
  82. })
  83. it('should parse video unavailable errors correctly', async () => {
  84. const error = new Error('Video is unavailable or private')
  85. const errorInfo = await errorHandler.handleError(error)
  86. expect(errorInfo.type).toBe(ERROR_TYPES.VIDEO_UNAVAILABLE)
  87. expect(errorInfo.message).toBe('Video is unavailable or has been removed')
  88. expect(errorInfo.recoverable).toBe(false)
  89. })
  90. it('should parse age-restricted errors correctly', async () => {
  91. const error = new Error('Sign in to confirm your age')
  92. const errorInfo = await errorHandler.handleError(error)
  93. expect(errorInfo.type).toBe(ERROR_TYPES.AGE_RESTRICTED)
  94. expect(errorInfo.message).toBe('Age-restricted content requires authentication')
  95. expect(errorInfo.recoverable).toBe(true)
  96. })
  97. it('should parse disk space errors correctly', async () => {
  98. const error = new Error('No space left on device')
  99. const errorInfo = await errorHandler.handleError(error)
  100. expect(errorInfo.type).toBe(ERROR_TYPES.DISK_SPACE)
  101. expect(errorInfo.message).toBe('Insufficient disk space')
  102. expect(errorInfo.recoverable).toBe(true)
  103. })
  104. it('should parse format errors correctly', async () => {
  105. const error = new Error('Requested format not available')
  106. const errorInfo = await errorHandler.handleError(error)
  107. expect(errorInfo.type).toBe(ERROR_TYPES.FORMAT_ERROR)
  108. expect(errorInfo.message).toBe('Requested video quality or format not available')
  109. expect(errorInfo.recoverable).toBe(true)
  110. })
  111. it('should add errors to history', async () => {
  112. const error = new Error('Test error')
  113. await errorHandler.handleError(error)
  114. expect(errorHandler.errorHistory).toHaveLength(1)
  115. expect(errorHandler.errorHistory[0].originalError).toBe(error)
  116. })
  117. it('should limit error history size', async () => {
  118. const handler = new ErrorHandler()
  119. handler.maxHistorySize = 3
  120. // Add more errors than the limit
  121. for (let i = 0; i < 5; i++) {
  122. await handler.handleError(new Error(`Error ${i}`))
  123. }
  124. expect(handler.errorHistory).toHaveLength(3)
  125. })
  126. it('should generate unique error IDs', () => {
  127. const id1 = errorHandler.generateErrorId()
  128. const id2 = errorHandler.generateErrorId()
  129. expect(id1).not.toBe(id2)
  130. expect(id1).toMatch(/^error_\d+_[a-z0-9]+$/)
  131. })
  132. it('should show error notifications', async () => {
  133. mockElectronAPI.showNotification.mockResolvedValue({ success: true })
  134. const error = new Error('Test error')
  135. await errorHandler.handleError(error, {}, { showNotification: true })
  136. expect(mockElectronAPI.showNotification).toHaveBeenCalled()
  137. })
  138. it('should show error dialogs for critical errors', async () => {
  139. mockElectronAPI.showErrorDialog.mockResolvedValue({ success: true, response: 0 })
  140. const error = new Error('Critical error')
  141. await errorHandler.handleError(error, {}, { showDialog: true })
  142. expect(mockElectronAPI.showErrorDialog).toHaveBeenCalled()
  143. })
  144. it('should dispatch in-app error events', async () => {
  145. let eventFired = false
  146. let eventDetail = null
  147. document.addEventListener('app-error', (event) => {
  148. eventFired = true
  149. eventDetail = event.detail
  150. })
  151. const error = new Error('Test error')
  152. await errorHandler.handleError(error, {}, { showInUI: true })
  153. expect(eventFired).toBe(true)
  154. expect(eventDetail).toHaveProperty('message')
  155. expect(eventDetail).toHaveProperty('type')
  156. })
  157. it('should handle binary errors specifically', async () => {
  158. mockElectronAPI.showNotification.mockResolvedValue({ success: true })
  159. mockElectronAPI.showErrorDialog.mockResolvedValue({ success: true, response: 0 })
  160. const errorInfo = await errorHandler.handleBinaryError('yt-dlp')
  161. expect(errorInfo.type).toBe(ERROR_TYPES.BINARY_MISSING)
  162. expect(errorInfo.message).toContain('yt-dlp')
  163. expect(mockElectronAPI.showNotification).toHaveBeenCalled()
  164. expect(mockElectronAPI.showErrorDialog).toHaveBeenCalled()
  165. })
  166. it('should handle network errors with retry logic', async () => {
  167. mockElectronAPI.showErrorDialog.mockResolvedValue({ success: true, response: 0 })
  168. const retryCallback = vi.fn().mockResolvedValue('success')
  169. const error = new Error('Network timeout')
  170. const result = await errorHandler.handleNetworkError(error, retryCallback, 1)
  171. expect(retryCallback).toHaveBeenCalled()
  172. })
  173. it('should provide error statistics', () => {
  174. // Add some test errors
  175. errorHandler.errorHistory = [
  176. { type: ERROR_TYPES.NETWORK, recoverable: true, timestamp: new Date() },
  177. { type: ERROR_TYPES.BINARY_MISSING, recoverable: false, timestamp: new Date() },
  178. { type: ERROR_TYPES.NETWORK, recoverable: true, timestamp: new Date() }
  179. ]
  180. const stats = errorHandler.getStats()
  181. expect(stats.total).toBe(3)
  182. expect(stats.byType.network).toBe(2)
  183. expect(stats.byType.binary_missing).toBe(1)
  184. expect(stats.recoverable).toBe(2)
  185. })
  186. it('should clear error history', () => {
  187. errorHandler.errorHistory = [{ test: 'error' }]
  188. errorHandler.clearHistory()
  189. expect(errorHandler.errorHistory).toEqual([])
  190. })
  191. it('should check if errors are recoverable', () => {
  192. const recoverableError = { recoverable: true }
  193. const nonRecoverableError = { recoverable: false }
  194. expect(errorHandler.isRecoverable(recoverableError)).toBe(true)
  195. expect(errorHandler.isRecoverable(nonRecoverableError)).toBe(false)
  196. })
  197. })
  198. describe('Error Types', () => {
  199. it('should have all required error types', () => {
  200. expect(ERROR_TYPES).toHaveProperty('NETWORK')
  201. expect(ERROR_TYPES).toHaveProperty('BINARY_MISSING')
  202. expect(ERROR_TYPES).toHaveProperty('PERMISSION')
  203. expect(ERROR_TYPES).toHaveProperty('VIDEO_UNAVAILABLE')
  204. expect(ERROR_TYPES).toHaveProperty('AGE_RESTRICTED')
  205. expect(ERROR_TYPES).toHaveProperty('DISK_SPACE')
  206. expect(ERROR_TYPES).toHaveProperty('FORMAT_ERROR')
  207. expect(ERROR_TYPES).toHaveProperty('UNKNOWN')
  208. })
  209. it('should have correct configuration for each type', () => {
  210. expect(ERROR_TYPES.NETWORK.recoverable).toBe(true)
  211. expect(ERROR_TYPES.BINARY_MISSING.recoverable).toBe(false)
  212. expect(ERROR_TYPES.PERMISSION.recoverable).toBe(true)
  213. expect(ERROR_TYPES.VIDEO_UNAVAILABLE.recoverable).toBe(false)
  214. })
  215. })
  216. })