Improved dashboard and music queue
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
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/command/command_parser.dart';
|
||||
import 'package:mumbullet/src/config/config.dart';
|
||||
import 'package:mumbullet/src/queue/music_queue.dart';
|
||||
import 'package:mumbullet/src/storage/database.dart';
|
||||
|
||||
/// Mock command context for testing
|
||||
class MockCommandContext {
|
||||
final String args;
|
||||
final int permissionLevel;
|
||||
final List<String> replies = [];
|
||||
|
||||
MockCommandContext({
|
||||
required this.args,
|
||||
this.permissionLevel = 2,
|
||||
});
|
||||
|
||||
Future<void> reply(String message) async {
|
||||
replies.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Command Handler with Downloader Integration Tests', () {
|
||||
late CommandHandler commandHandler;
|
||||
late YoutubeDownloader downloader;
|
||||
late DatabaseManager database;
|
||||
late BotConfig config;
|
||||
late Directory tempDir;
|
||||
late String tempDbPath;
|
||||
late CommandParser commandParser;
|
||||
late MusicQueue musicQueue;
|
||||
|
||||
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 integration tests: yt-dlp not found in PATH');
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
// Create temporary directory for cache
|
||||
tempDir = await Directory.systemTemp.createTemp('mumbullet_cmd_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 components
|
||||
database = DatabaseManager(tempDbPath);
|
||||
downloader = YoutubeDownloader(config, database);
|
||||
commandParser = CommandParser();
|
||||
musicQueue = MusicQueue();
|
||||
|
||||
// Create command handler
|
||||
commandHandler = CommandHandler(
|
||||
commandParser,
|
||||
musicQueue,
|
||||
downloader,
|
||||
config,
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
// Clean up
|
||||
database.close();
|
||||
if (tempDir.existsSync()) {
|
||||
await tempDir.delete(recursive: true);
|
||||
}
|
||||
});
|
||||
|
||||
group('Play Command Integration', () {
|
||||
test('should download and play valid YouTube URL', () async {
|
||||
const testUrl = 'https://www.youtube.com/watch?v=jNQXAC9IVRw';
|
||||
final context = MockCommandContext(args: testUrl);
|
||||
|
||||
// Get the play command
|
||||
final playCommand = commandParser.getCommand('play');
|
||||
expect(playCommand, isNotNull);
|
||||
|
||||
// Execute play command
|
||||
await playCommand!.execute(context);
|
||||
|
||||
// Verify responses
|
||||
expect(context.replies.length, equals(2));
|
||||
expect(context.replies[0], contains('Downloading audio from:'));
|
||||
expect(context.replies[0], contains(testUrl));
|
||||
expect(context.replies[1], startsWith('Now playing:'));
|
||||
|
||||
// Verify queue state
|
||||
expect(musicQueue.currentSong, isNotNull);
|
||||
expect(musicQueue.currentSong!.url, equals(testUrl));
|
||||
|
||||
// Verify file was downloaded
|
||||
final file = File(musicQueue.currentSong!.filePath);
|
||||
expect(file.existsSync(), isTrue);
|
||||
}, timeout: Timeout(Duration(minutes: 2)));
|
||||
|
||||
test('should handle HTML anchor tag URLs in play command', () async {
|
||||
const testUrl = 'https://www.youtube.com/watch?v=jNQXAC9IVRw';
|
||||
const htmlInput = '<a href="$testUrl">YouTube Video</a>';
|
||||
final context = MockCommandContext(args: htmlInput);
|
||||
|
||||
final playCommand = commandParser.getCommand('play');
|
||||
await playCommand!.execute(context);
|
||||
|
||||
// Should extract URL from HTML and download
|
||||
expect(context.replies.length, equals(2));
|
||||
expect(context.replies[0], contains(testUrl));
|
||||
expect(context.replies[1], startsWith('Now playing:'));
|
||||
|
||||
expect(musicQueue.currentSong, isNotNull);
|
||||
expect(musicQueue.currentSong!.url, equals(testUrl));
|
||||
}, timeout: Timeout(Duration(minutes: 2)));
|
||||
|
||||
test('should handle play command with invalid URL', () async {
|
||||
const invalidUrl = 'https://www.youtube.com/watch?v=InvalidVideoId123';
|
||||
final context = MockCommandContext(args: invalidUrl);
|
||||
|
||||
final playCommand = commandParser.getCommand('play');
|
||||
await playCommand!.execute(context);
|
||||
|
||||
// Should show download attempt and then error
|
||||
expect(context.replies.length, equals(2));
|
||||
expect(context.replies[0], contains('Downloading audio from:'));
|
||||
expect(context.replies[1], startsWith('Failed to download or play'));
|
||||
|
||||
// Queue should be empty
|
||||
expect(musicQueue.currentSong, isNull);
|
||||
}, timeout: Timeout(Duration(minutes: 1)));
|
||||
});
|
||||
|
||||
group('Queue Command Integration', () {
|
||||
test('should download and queue valid YouTube URL', () async {
|
||||
const testUrl = 'https://www.youtube.com/watch?v=jNQXAC9IVRw';
|
||||
final context = MockCommandContext(args: testUrl, permissionLevel: 1);
|
||||
|
||||
final queueCommand = commandParser.getCommand('queue');
|
||||
expect(queueCommand, isNotNull);
|
||||
|
||||
await queueCommand!.execute(context);
|
||||
|
||||
// Verify responses
|
||||
expect(context.replies.length, equals(2));
|
||||
expect(context.replies[0], contains('Adding to queue:'));
|
||||
expect(context.replies[1], startsWith('Now playing:')); // First song starts playing
|
||||
|
||||
// Verify queue state
|
||||
expect(musicQueue.currentSong, isNotNull);
|
||||
expect(musicQueue.currentSong!.url, equals(testUrl));
|
||||
}, timeout: Timeout(Duration(minutes: 2)));
|
||||
|
||||
test('should queue multiple songs', () async {
|
||||
const testUrls = [
|
||||
'https://www.youtube.com/watch?v=jNQXAC9IVRw',
|
||||
'https://youtu.be/jNQXAC9IVRw', // Same video, different format
|
||||
];
|
||||
|
||||
for (int i = 0; i < testUrls.length; i++) {
|
||||
final context = MockCommandContext(args: testUrls[i], permissionLevel: 1);
|
||||
final queueCommand = commandParser.getCommand('queue');
|
||||
|
||||
await queueCommand!.execute(context);
|
||||
|
||||
if (i == 0) {
|
||||
// First song starts playing
|
||||
expect(context.replies.last, startsWith('Now playing:'));
|
||||
} else {
|
||||
// Subsequent songs are queued (but may use cache)
|
||||
expect(context.replies.last, anyOf(
|
||||
startsWith('Now playing:'), // If cached, might start immediately
|
||||
startsWith('Added to queue at position'),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Verify queue has songs
|
||||
final queue = musicQueue.getQueue();
|
||||
expect(queue.length, greaterThanOrEqualTo(1));
|
||||
}, timeout: Timeout(Duration(minutes: 3)));
|
||||
|
||||
test('should handle queue command with invalid URL', () async {
|
||||
const invalidUrl = 'not-a-valid-url';
|
||||
final context = MockCommandContext(args: invalidUrl, permissionLevel: 1);
|
||||
|
||||
final queueCommand = commandParser.getCommand('queue');
|
||||
await queueCommand!.execute(context);
|
||||
|
||||
// Should show error
|
||||
expect(context.replies.length, equals(2));
|
||||
expect(context.replies[0], contains('Adding to queue:'));
|
||||
expect(context.replies[1], startsWith('Failed to download or queue'));
|
||||
|
||||
// Queue should be empty
|
||||
expect(musicQueue.getQueue(), isEmpty);
|
||||
}, timeout: Timeout(Duration(minutes: 1)));
|
||||
});
|
||||
|
||||
group('Shuffle Command Integration', () {
|
||||
test('should shuffle cached songs', () async {
|
||||
// First, download a few songs to cache
|
||||
const testUrls = [
|
||||
'https://www.youtube.com/watch?v=jNQXAC9IVRw',
|
||||
];
|
||||
|
||||
// Download songs to cache
|
||||
for (final url in testUrls) {
|
||||
await downloader.download(url);
|
||||
}
|
||||
|
||||
// Now test shuffle command
|
||||
final context = MockCommandContext(args: '', permissionLevel: 2);
|
||||
final shuffleCommand = commandParser.getCommand('shuffle');
|
||||
|
||||
await shuffleCommand!.execute(context);
|
||||
|
||||
// Should have shuffled and started playing
|
||||
expect(context.replies.length, equals(1));
|
||||
expect(context.replies[0], contains('Added'));
|
||||
expect(context.replies[0], contains('shuffled songs'));
|
||||
expect(context.replies[0], contains('Now playing:'));
|
||||
|
||||
// Queue should have songs
|
||||
final queue = musicQueue.getQueue();
|
||||
expect(queue.length, greaterThan(0));
|
||||
expect(musicQueue.currentSong, isNotNull);
|
||||
}, timeout: Timeout(Duration(minutes: 2)));
|
||||
|
||||
test('should handle shuffle with no cached songs', () async {
|
||||
// Clear any existing cache
|
||||
await downloader.clearCache();
|
||||
|
||||
final context = MockCommandContext(args: '', permissionLevel: 2);
|
||||
final shuffleCommand = commandParser.getCommand('shuffle');
|
||||
|
||||
await shuffleCommand!.execute(context);
|
||||
|
||||
// Should indicate no cached songs
|
||||
expect(context.replies.length, equals(1));
|
||||
expect(context.replies[0], contains('No cached songs found'));
|
||||
});
|
||||
});
|
||||
|
||||
group('URL Format Handling', () {
|
||||
test('should handle various YouTube URL formats in commands', () async {
|
||||
final testCases = [
|
||||
'https://www.youtube.com/watch?v=jNQXAC9IVRw',
|
||||
'https://youtube.com/watch?v=jNQXAC9IVRw',
|
||||
'https://youtu.be/jNQXAC9IVRw',
|
||||
'https://m.youtube.com/watch?v=jNQXAC9IVRw',
|
||||
'<a href="https://www.youtube.com/watch?v=jNQXAC9IVRw">Video Link</a>',
|
||||
];
|
||||
|
||||
for (final testUrl in testCases) {
|
||||
// Clear queue for each test
|
||||
musicQueue.clear();
|
||||
|
||||
final context = MockCommandContext(args: testUrl);
|
||||
final playCommand = commandParser.getCommand('play');
|
||||
|
||||
await playCommand!.execute(context);
|
||||
|
||||
// Should successfully download and play
|
||||
expect(context.replies.length, equals(2));
|
||||
expect(context.replies[1], startsWith('Now playing:'));
|
||||
expect(musicQueue.currentSong, isNotNull);
|
||||
|
||||
// All should result in the same video title (same video)
|
||||
expect(musicQueue.currentSong!.title, isNotEmpty);
|
||||
}
|
||||
}, timeout: Timeout(Duration(minutes: 5)));
|
||||
});
|
||||
|
||||
group('Permission Handling', () {
|
||||
test('should respect permission levels for commands', () async {
|
||||
const testUrl = 'https://www.youtube.com/watch?v=jNQXAC9IVRw';
|
||||
|
||||
// Test play command with insufficient permissions
|
||||
final lowPermContext = MockCommandContext(args: testUrl, permissionLevel: 1);
|
||||
final playCommand = commandParser.getCommand('play');
|
||||
|
||||
// Play command requires level 2, user has level 1
|
||||
expect(playCommand!.requiredPermissionLevel, equals(2));
|
||||
|
||||
// This would normally be handled by the command parser's permission check
|
||||
// but we're testing the command directly, so we'll verify the requirement
|
||||
expect(lowPermContext.permissionLevel, lessThan(playCommand.requiredPermissionLevel));
|
||||
|
||||
// Test queue command with sufficient permissions
|
||||
final queueContext = MockCommandContext(args: testUrl, permissionLevel: 1);
|
||||
final queueCommand = commandParser.getCommand('queue');
|
||||
|
||||
expect(queueCommand!.requiredPermissionLevel, equals(1));
|
||||
expect(queueContext.permissionLevel, greaterThanOrEqualTo(queueCommand.requiredPermissionLevel));
|
||||
|
||||
await queueCommand.execute(queueContext);
|
||||
expect(queueContext.replies.length, equals(2));
|
||||
expect(queueContext.replies[1], startsWith('Now playing:'));
|
||||
}, timeout: Timeout(Duration(minutes: 2)));
|
||||
});
|
||||
}, skip: Platform.environment['SKIP_INTEGRATION_TESTS'] == 'true');
|
||||
}
|
||||
Reference in New Issue
Block a user