test-downloads.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. #!/usr/bin/env node
  2. /**
  3. * Automated Download Test Script
  4. * Tests various download scenarios programmatically
  5. */
  6. const { spawn } = require('child_process')
  7. const fs = require('fs')
  8. const path = require('path')
  9. const os = require('os')
  10. // Test configuration
  11. const TEST_URLS = {
  12. short: 'https://www.youtube.com/watch?v=jNQXAC9IVRw', // Me at the zoo (0:19)
  13. medium: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', // Rick Astley (3:33)
  14. long: 'https://www.youtube.com/watch?v=_OBlgSz8sSM', // Big Buck Bunny (9:56)
  15. playlist: 'https://www.youtube.com/playlist?list=PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf',
  16. shorts: 'https://www.youtube.com/shorts/dQw4w9WgXcQ',
  17. vimeo: 'https://vimeo.com/148751763'
  18. }
  19. const TEST_OUTPUT_DIR = path.join(os.tmpdir(), 'grabzilla-test-downloads')
  20. class DownloadTester {
  21. constructor() {
  22. this.results = []
  23. this.testStartTime = Date.now()
  24. }
  25. /**
  26. * Setup test environment
  27. */
  28. async setup() {
  29. console.log('🔧 Setting up test environment...\n')
  30. // Create test output directory
  31. if (!fs.existsSync(TEST_OUTPUT_DIR)) {
  32. fs.mkdirSync(TEST_OUTPUT_DIR, { recursive: true })
  33. console.log(`✅ Created test directory: ${TEST_OUTPUT_DIR}`)
  34. } else {
  35. console.log(`✅ Using existing test directory: ${TEST_OUTPUT_DIR}`)
  36. }
  37. // Check binaries exist
  38. const binariesPath = path.join(__dirname, '../../binaries')
  39. const ytdlp = path.join(binariesPath, process.platform === 'win32' ? 'yt-dlp.exe' : 'yt-dlp')
  40. const ffmpeg = path.join(binariesPath, process.platform === 'win32' ? 'ffmpeg.exe' : 'ffmpeg')
  41. if (!fs.existsSync(ytdlp)) {
  42. throw new Error('❌ yt-dlp binary not found. Run: npm run setup')
  43. }
  44. if (!fs.existsSync(ffmpeg)) {
  45. throw new Error('❌ ffmpeg binary not found. Run: npm run setup')
  46. }
  47. console.log('✅ Binaries found\n')
  48. }
  49. /**
  50. * Run a single download test
  51. */
  52. async runDownloadTest(testName, url, options = {}) {
  53. console.log(`\n📥 Running test: ${testName}`)
  54. console.log(` URL: ${url}`)
  55. console.log(` Options: ${JSON.stringify(options)}`)
  56. const startTime = Date.now()
  57. const result = {
  58. name: testName,
  59. url,
  60. options,
  61. startTime,
  62. status: 'running'
  63. }
  64. try {
  65. // Simulate download by getting video info
  66. const info = await this.getVideoInfo(url)
  67. const duration = Date.now() - startTime
  68. result.duration = duration
  69. result.status = 'passed'
  70. result.videoInfo = info
  71. console.log(`✅ Test passed in ${(duration / 1000).toFixed(2)}s`)
  72. console.log(` Title: ${info.title}`)
  73. console.log(` Duration: ${info.duration}`)
  74. } catch (error) {
  75. const duration = Date.now() - startTime
  76. result.duration = duration
  77. result.status = 'failed'
  78. result.error = error.message
  79. console.log(`❌ Test failed in ${(duration / 1000).toFixed(2)}s`)
  80. console.log(` Error: ${error.message}`)
  81. }
  82. this.results.push(result)
  83. return result
  84. }
  85. /**
  86. * Get video info using yt-dlp
  87. */
  88. async getVideoInfo(url) {
  89. return new Promise((resolve, reject) => {
  90. const ytdlpPath = path.join(
  91. __dirname,
  92. '../../binaries',
  93. process.platform === 'win32' ? 'yt-dlp.exe' : 'yt-dlp'
  94. )
  95. const args = ['--dump-json', '--no-warnings', url]
  96. const childProcess = spawn(ytdlpPath, args)
  97. let stdout = ''
  98. let stderr = ''
  99. childProcess.stdout.on('data', (data) => {
  100. stdout += data.toString()
  101. })
  102. childProcess.stderr.on('data', (data) => {
  103. stderr += data.toString()
  104. })
  105. childProcess.on('close', (code) => {
  106. if (code === 0) {
  107. try {
  108. const info = JSON.parse(stdout)
  109. resolve({
  110. title: info.title || 'Unknown',
  111. duration: info.duration ? this.formatDuration(info.duration) : 'Unknown',
  112. format: info.format || 'Unknown',
  113. filesize: info.filesize ? this.formatFilesize(info.filesize) : 'Unknown'
  114. })
  115. } catch (error) {
  116. reject(new Error('Failed to parse video info'))
  117. }
  118. } else {
  119. reject(new Error(stderr || 'Failed to get video info'))
  120. }
  121. })
  122. process.on('error', (error) => {
  123. reject(error)
  124. })
  125. })
  126. }
  127. /**
  128. * Format duration in seconds to MM:SS
  129. */
  130. formatDuration(seconds) {
  131. const mins = Math.floor(seconds / 60)
  132. const secs = Math.floor(seconds % 60)
  133. return `${mins}:${secs.toString().padStart(2, '0')}`
  134. }
  135. /**
  136. * Format filesize in bytes to human readable
  137. */
  138. formatFilesize(bytes) {
  139. if (!bytes) return 'Unknown'
  140. const mb = bytes / (1024 * 1024)
  141. return `${mb.toFixed(2)} MB`
  142. }
  143. /**
  144. * Test single video download
  145. */
  146. async testSingleDownload() {
  147. console.log('\n' + '='.repeat(60))
  148. console.log('TEST SUITE: Single Video Downloads')
  149. console.log('='.repeat(60))
  150. await this.runDownloadTest('Short video', TEST_URLS.short)
  151. await this.runDownloadTest('Medium video', TEST_URLS.medium)
  152. await this.runDownloadTest('Long video', TEST_URLS.long)
  153. }
  154. /**
  155. * Test playlist
  156. */
  157. async testPlaylist() {
  158. console.log('\n' + '='.repeat(60))
  159. console.log('TEST SUITE: Playlist Downloads')
  160. console.log('='.repeat(60))
  161. await this.runDownloadTest('Small playlist', TEST_URLS.playlist)
  162. }
  163. /**
  164. * Test Shorts
  165. */
  166. async testShorts() {
  167. console.log('\n' + '='.repeat(60))
  168. console.log('TEST SUITE: YouTube Shorts')
  169. console.log('='.repeat(60))
  170. await this.runDownloadTest('Shorts URL', TEST_URLS.shorts)
  171. }
  172. /**
  173. * Test Vimeo
  174. */
  175. async testVimeo() {
  176. console.log('\n' + '='.repeat(60))
  177. console.log('TEST SUITE: Vimeo Support')
  178. console.log('='.repeat(60))
  179. await this.runDownloadTest('Vimeo video', TEST_URLS.vimeo)
  180. }
  181. /**
  182. * Test error handling
  183. */
  184. async testErrorHandling() {
  185. console.log('\n' + '='.repeat(60))
  186. console.log('TEST SUITE: Error Handling')
  187. console.log('='.repeat(60))
  188. await this.runDownloadTest(
  189. 'Invalid URL',
  190. 'https://www.youtube.com/watch?v=invalid123'
  191. )
  192. await this.runDownloadTest(
  193. 'Malformed URL',
  194. 'not-a-url'
  195. )
  196. }
  197. /**
  198. * Generate test report
  199. */
  200. generateReport() {
  201. const totalDuration = Date.now() - this.testStartTime
  202. console.log('\n' + '='.repeat(60))
  203. console.log('TEST REPORT')
  204. console.log('='.repeat(60))
  205. const passed = this.results.filter(r => r.status === 'passed').length
  206. const failed = this.results.filter(r => r.status === 'failed').length
  207. const total = this.results.length
  208. console.log(`\n📊 Summary:`)
  209. console.log(` Total tests: ${total}`)
  210. console.log(` Passed: ${passed} ✅`)
  211. console.log(` Failed: ${failed} ${failed > 0 ? '❌' : ''}`)
  212. console.log(` Success rate: ${((passed / total) * 100).toFixed(1)}%`)
  213. console.log(` Total time: ${(totalDuration / 1000).toFixed(2)}s`)
  214. console.log(`\n📝 Detailed Results:`)
  215. this.results.forEach((result, index) => {
  216. const icon = result.status === 'passed' ? '✅' : '❌'
  217. console.log(` ${index + 1}. ${icon} ${result.name} (${(result.duration / 1000).toFixed(2)}s)`)
  218. if (result.status === 'failed') {
  219. console.log(` Error: ${result.error}`)
  220. }
  221. })
  222. // Save report to file
  223. const reportPath = path.join(__dirname, 'test-report.json')
  224. fs.writeFileSync(reportPath, JSON.stringify(this.results, null, 2))
  225. console.log(`\n💾 Full report saved to: ${reportPath}`)
  226. return {
  227. passed,
  228. failed,
  229. total,
  230. duration: totalDuration
  231. }
  232. }
  233. /**
  234. * Cleanup test environment
  235. */
  236. async cleanup() {
  237. console.log('\n🧹 Cleaning up...')
  238. // Note: Not deleting test directory to allow manual inspection
  239. console.log(` Test files preserved in: ${TEST_OUTPUT_DIR}`)
  240. }
  241. }
  242. /**
  243. * Main test execution
  244. */
  245. async function main() {
  246. console.log('🚀 GrabZilla Download Test Suite')
  247. console.log('================================\n')
  248. const tester = new DownloadTester()
  249. try {
  250. // Setup
  251. await tester.setup()
  252. // Run test suites
  253. await tester.testSingleDownload()
  254. await tester.testPlaylist()
  255. await tester.testShorts()
  256. await tester.testVimeo()
  257. await tester.testErrorHandling()
  258. // Generate report
  259. const summary = tester.generateReport()
  260. // Cleanup
  261. await tester.cleanup()
  262. // Exit with appropriate code
  263. process.exit(summary.failed > 0 ? 1 : 0)
  264. } catch (error) {
  265. console.error('\n❌ Test suite failed:', error.message)
  266. console.error(error.stack)
  267. process.exit(1)
  268. }
  269. }
  270. // Run if executed directly
  271. if (require.main === module) {
  272. main()
  273. }
  274. module.exports = DownloadTester