import 'dart:io'; import 'package:test/test.dart'; import 'package:path/path.dart' as path; import 'package:mumbullet/src/audio/downloader.dart'; import 'package:mumbullet/src/command/command_handler.dart'; import 'package:mumbullet/src/config/config.dart'; import 'package:mumbullet/src/storage/database.dart'; void main() { group('Downloader Integration Tests', () { late YoutubeDownloader downloader; late DatabaseManager database; late BotConfig config; late Directory tempDir; late String tempDbPath; setUpAll(() async { // Check if yt-dlp is available try { final result = await Process.run('yt-dlp', ['--version']); if (result.exitCode != 0) { throw Exception('yt-dlp not available'); } } catch (e) { print('Skipping yt-dlp tests: yt-dlp not found in PATH'); return; } }); setUp(() async { // Create temporary directory for cache tempDir = await Directory.systemTemp.createTemp('mumbullet_integration_'); // Create temporary database tempDbPath = path.join(tempDir.path, 'test.db'); // Create test configuration config = BotConfig( commandPrefix: '!', defaultPermissionLevel: 1, maxQueueSize: 10, cacheDirectory: tempDir.path, maxCacheSizeGb: 0.1, // 100MB for testing ); // Initialize database database = DatabaseManager(tempDbPath); // Create downloader downloader = YoutubeDownloader(config, database); }); tearDown(() async { // Clean up try { database.close(); } catch (e) { // Ignore close errors } if (tempDir.existsSync()) { await tempDir.delete(recursive: true); } }); group('Real YouTube Downloads', () { test('should successfully download the test YouTube video', () async { const testUrl = 'https://www.youtube.com/watch?v=jNQXAC9IVRw'; // "Me at the zoo" final song = await downloader.download(testUrl); // Verify the download worked expect(song.url, equals(testUrl)); expect(song.title, equals('Me at the zoo')); expect(song.duration, equals(19)); expect(song.filePath, isNotEmpty); expect(song.id, greaterThan(0)); // Verify the file exists and has content final file = File(song.filePath); expect(file.existsSync(), isTrue); expect(file.lengthSync(), greaterThan(1000000)); // Should be > 1MB print('✓ Successfully downloaded: ${song.title} (${song.duration}s)'); print(' File: ${song.filePath} (${(file.lengthSync() / 1024 / 1024).toStringAsFixed(1)} MB)'); }, timeout: Timeout(Duration(minutes: 2))); test('should handle different YouTube URL formats for same video', () async { final testUrls = [ 'https://www.youtube.com/watch?v=jNQXAC9IVRw', 'https://youtu.be/jNQXAC9IVRw', 'https://m.youtube.com/watch?v=jNQXAC9IVRw', ]; final songs = []; for (final url in testUrls) { final song = await downloader.download(url); songs.add(song); expect(song.title, equals('Me at the zoo')); expect(song.duration, equals(19)); print('✓ URL format test: $url -> ${song.title}'); } // All should have the same title and duration for (final song in songs) { expect(song.title, equals(songs.first.title)); expect(song.duration, equals(songs.first.duration)); } }, timeout: Timeout(Duration(minutes: 4))); test('should properly cache downloads', () async { const testUrl = 'https://www.youtube.com/watch?v=jNQXAC9IVRw'; // First download - should take time final stopwatch1 = Stopwatch()..start(); final song1 = await downloader.download(testUrl); stopwatch1.stop(); // Second download - should be much faster (cached) final stopwatch2 = Stopwatch()..start(); final song2 = await downloader.download(testUrl); stopwatch2.stop(); // Verify same song returned expect(song1.id, equals(song2.id)); expect(song1.filePath, equals(song2.filePath)); expect(song1.title, equals(song2.title)); // Second call should be much faster expect(stopwatch2.elapsedMilliseconds, lessThan(stopwatch1.elapsedMilliseconds ~/ 3)); print('✓ Cache test: First download: ${stopwatch1.elapsedMilliseconds}ms, Second: ${stopwatch2.elapsedMilliseconds}ms'); }, timeout: Timeout(Duration(minutes: 3))); }); group('Error Handling', () { test('should handle invalid YouTube URLs', () async { final invalidUrls = [ 'https://www.youtube.com/watch?v=InvalidVideoId123456', 'https://www.youtube.com/watch?v=', 'not-a-url-at-all', 'https://invalid-domain.com/video', ]; for (final url in invalidUrls) { try { await downloader.download(url); fail('Should have thrown exception for invalid URL: $url'); } catch (e) { expect(e, isA()); print('✓ Correctly rejected invalid URL: $url'); } } }); test('should handle network issues gracefully', () async { // This URL should timeout or fail const problematicUrl = 'https://httpstat.us/500'; try { await downloader.download(problematicUrl); fail('Should have thrown exception for problematic URL'); } catch (e) { expect(e, isA()); print('✓ Correctly handled network issue: ${e.toString()}'); } }, timeout: Timeout(Duration(seconds: 30))); }); group('Cache Management', () { test('should provide accurate cache statistics', () async { // Initially empty final initialStats = await downloader.getCacheStats(); expect(initialStats['songCount'], equals(0)); expect(initialStats['totalSizeBytes'], equals(0)); // Download a video const testUrl = 'https://www.youtube.com/watch?v=jNQXAC9IVRw'; final song = await downloader.download(testUrl); // Check updated stats final updatedStats = await downloader.getCacheStats(); expect(updatedStats['songCount'], equals(1)); expect(updatedStats['totalSizeBytes'], greaterThan(1000000)); // > 1MB // Verify file size matches final file = File(song.filePath); expect(updatedStats['totalSizeBytes'], equals(file.lengthSync())); print('✓ Cache stats: ${updatedStats['songCount']} songs, ${updatedStats['totalSizeMb']} MB'); }, timeout: Timeout(Duration(minutes: 2))); test('should clear cache completely', () async { // Download a video const testUrl = 'https://www.youtube.com/watch?v=jNQXAC9IVRw'; final song = await downloader.download(testUrl); final filePath = song.filePath; // Verify file exists expect(File(filePath).existsSync(), isTrue); // Clear cache await downloader.clearCache(); // Verify cache is empty final statsAfter = await downloader.getCacheStats(); expect(statsAfter['songCount'], equals(0)); expect(statsAfter['totalSizeBytes'], equals(0)); // Verify file was deleted expect(File(filePath).existsSync(), isFalse); print('✓ Cache cleared successfully'); }, timeout: Timeout(Duration(minutes: 2))); test('should handle missing cached files gracefully', () async { const testUrl = 'https://www.youtube.com/watch?v=jNQXAC9IVRw'; // Download a video final song1 = await downloader.download(testUrl); final originalPath = song1.filePath; // Manually delete the file await File(originalPath).delete(); // Try to download again - should re-download final song2 = await downloader.download(testUrl); // Should have same metadata but new file expect(song2.title, equals(song1.title)); expect(song2.duration, equals(song1.duration)); expect(File(song2.filePath).existsSync(), isTrue); print('✓ Re-download after file deletion: ${song2.title}'); }, timeout: Timeout(Duration(minutes: 3))); }); group('URL Extraction', () { test('should extract URLs from HTML anchor tags', () async { const testUrl = 'https://www.youtube.com/watch?v=jNQXAC9IVRw'; const htmlInput = 'YouTube Video'; // Test the regex pattern used in command handler final aTagRegex = RegExp(r']*>.*?', caseSensitive: false); final match = aTagRegex.firstMatch(htmlInput); expect(match, isNotNull); expect(match!.group(1), equals(testUrl)); print('✓ HTML URL extraction works correctly'); }); }); }, skip: Platform.environment['SKIP_INTEGRATION_TESTS'] == 'true'); }