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/config/config.dart'; import 'package:mumbullet/src/storage/database.dart'; void main() { group('YoutubeDownloader Simple 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_test_'); // 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('Basic Functionality', () { test('should download a valid YouTube video and return Song object', () async { const testUrl = 'https://www.youtube.com/watch?v=jNQXAC9IVRw'; // "Me at the zoo" final song = await downloader.download(testUrl); // Verify basic song properties expect(song.url, equals(testUrl)); expect(song.title, isNotEmpty); expect(song.title, isNot(equals('Unknown Title'))); expect(song.duration, greaterThan(0)); expect(song.filePath, isNotEmpty); expect(song.id, greaterThan(0)); expect(song.addedAt, isNotNull); // Verify file path structure expect(song.filePath, startsWith(tempDir.path)); expect(song.filePath, endsWith('.wav')); print('Downloaded: ${song.title} (${song.duration}s) -> ${song.filePath}'); }, timeout: Timeout(Duration(minutes: 2))); test('should use cache on second download', () async { const testUrl = 'https://www.youtube.com/watch?v=jNQXAC9IVRw'; // First download final song1 = await downloader.download(testUrl); // Second download (should use cache) final song2 = await downloader.download(testUrl); // Should return same song from cache expect(song1.id, equals(song2.id)); expect(song1.title, equals(song2.title)); expect(song1.filePath, equals(song2.filePath)); print('Cache test passed: ${song1.title}'); }, timeout: Timeout(Duration(minutes: 3))); test('should handle invalid URLs gracefully', () async { final invalidUrls = [ 'not-a-url', 'https://invalid-domain.com/video', 'https://www.youtube.com/watch?v=InvalidVideoId123', ]; 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 handled invalid URL: $url - ${e.toString()}'); } } }); test('should provide cache statistics', () async { // Initially empty final initialStats = await downloader.getCacheStats(); expect(initialStats['songCount'], equals(0)); // Download a video const testUrl = 'https://www.youtube.com/watch?v=jNQXAC9IVRw'; await downloader.download(testUrl); // Check updated stats final updatedStats = await downloader.getCacheStats(); expect(updatedStats['songCount'], equals(1)); expect(updatedStats['totalSizeBytes'], greaterThan(0)); print('Cache stats: ${updatedStats['songCount']} songs, ${updatedStats['totalSizeMb']} MB'); }, timeout: Timeout(Duration(minutes: 2))); test('should clear cache', () async { // Download a video const testUrl = 'https://www.youtube.com/watch?v=jNQXAC9IVRw'; await downloader.download(testUrl); // Verify cache has content final statsBefore = await downloader.getCacheStats(); expect(statsBefore['songCount'], greaterThan(0)); // Clear cache await downloader.clearCache(); // Verify cache is empty final statsAfter = await downloader.getCacheStats(); expect(statsAfter['songCount'], equals(0)); expect(statsAfter['totalSizeBytes'], equals(0)); print('Cache cleared successfully'); }, timeout: Timeout(Duration(minutes: 2))); }); group('URL Format Variations', () { test('should handle different YouTube URL formats', () async { final testUrls = [ 'https://www.youtube.com/watch?v=jNQXAC9IVRw', 'https://youtu.be/jNQXAC9IVRw', ]; String? firstTitle; for (final url in testUrls) { final song = await downloader.download(url); expect(song.title, isNotEmpty); expect(song.duration, greaterThan(0)); if (firstTitle == null) { firstTitle = song.title; } else { // All URLs should resolve to the same video expect(song.title, equals(firstTitle)); } print('URL format test: $url -> ${song.title}'); } }, timeout: Timeout(Duration(minutes: 3))); }); group('Edge Cases', () { test('should handle re-download when cached file is missing', () 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 (simulating external deletion) if (File(originalPath).existsSync()) { await File(originalPath).delete(); } // Try to download again - should re-download final song2 = await downloader.download(testUrl); // Should have same title but may have different file path expect(song2.title, equals(song1.title)); expect(song2.filePath, isNotEmpty); print('Re-download test passed: ${song2.title}'); }, timeout: Timeout(Duration(minutes: 3))); }); }, skip: Platform.environment['SKIP_INTEGRATION_TESTS'] == 'true'); }