273 lines
8.8 KiB
Dart
273 lines
8.8 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:mumbullet/mumbullet.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
/// Integration test for Mumble connection
|
|
///
|
|
/// This test:
|
|
/// - Requires Docker to be running
|
|
/// - Uses a dedicated docker-compose.test.yml file
|
|
/// - Creates a Mumble server with predefined channels
|
|
/// - Tests connection, authentication, and channel navigation
|
|
///
|
|
/// The server is automatically started before tests and stopped after tests
|
|
/// using docker-compose commands.
|
|
@Tags(['docker'])
|
|
void main() {
|
|
// Use setUpAll and tearDownAll to ensure Docker is only started/stopped once for all tests
|
|
late MumbleConnection connection;
|
|
late AppConfig config;
|
|
|
|
const rootPath = '/home/nate/source/non-work/mumbullet';
|
|
const configPath = '$rootPath/test/fixtures/test_config.json';
|
|
|
|
setUpAll(() async {
|
|
// Create cache directory if it doesn't exist
|
|
final cacheDir = Directory('$rootPath/test/fixtures/cache');
|
|
if (!await cacheDir.exists()) {
|
|
await cacheDir.create(recursive: true);
|
|
}
|
|
|
|
// Check if Docker is running
|
|
try {
|
|
final checkDocker = await Process.run('docker', ['info']);
|
|
if (checkDocker.exitCode != 0) {
|
|
fail('Docker is not running. Please start Docker and try again.');
|
|
}
|
|
} catch (e) {
|
|
fail('Failed to check Docker status: $e. Is Docker installed?');
|
|
}
|
|
|
|
// Start Docker container using test-specific compose file
|
|
print('Starting Mumble server container for tests...');
|
|
final dockerProcess = await Process.run(
|
|
'docker-compose',
|
|
['-f', 'test/integration/docker-compose.test.yml', 'up', '-d'],
|
|
workingDirectory: rootPath
|
|
);
|
|
|
|
if (dockerProcess.exitCode != 0) {
|
|
fail('Failed to start Docker container: ${dockerProcess.stderr}');
|
|
}
|
|
|
|
// Wait for server to start
|
|
print('Waiting for Mumble server to start...');
|
|
await Future.delayed(Duration(seconds: 5));
|
|
});
|
|
|
|
tearDownAll(() async {
|
|
// Stop Docker container
|
|
print('Stopping Mumble server container...');
|
|
final stopProcess = await Process.run(
|
|
'docker-compose',
|
|
['-f', 'test/integration/docker-compose.test.yml', 'down', '-v'],
|
|
workingDirectory: rootPath
|
|
);
|
|
|
|
if (stopProcess.exitCode != 0) {
|
|
print('Warning: Failed to stop Docker container: ${stopProcess.stderr}');
|
|
}
|
|
});
|
|
|
|
setUp(() async {
|
|
// Load test configuration
|
|
config = AppConfig.fromFile(configPath);
|
|
|
|
// Create connection
|
|
connection = MumbleConnection(config.mumble);
|
|
});
|
|
|
|
tearDown(() async {
|
|
// Disconnect from server
|
|
if (connection.isConnected) {
|
|
await connection.disconnect();
|
|
}
|
|
});
|
|
|
|
test('Should connect to Mumble server with password', () async {
|
|
// Setup test expectations
|
|
final connectionCompleter = Completer<bool>();
|
|
|
|
// Listen for connection state changes
|
|
connection.connectionState.listen((isConnected) {
|
|
if (isConnected && !connectionCompleter.isCompleted) {
|
|
connectionCompleter.complete(true);
|
|
}
|
|
});
|
|
|
|
// Attempt to connect with password
|
|
await connection.connect();
|
|
|
|
// Wait for connection or timeout
|
|
expect(
|
|
await connectionCompleter.future.timeout(Duration(seconds: 10), onTimeout: () => false),
|
|
isTrue,
|
|
reason: 'Failed to connect to Mumble server with password',
|
|
);
|
|
|
|
// Verify connection is established
|
|
expect(connection.isConnected, isTrue);
|
|
|
|
// Verify we can get channel information
|
|
expect(connection.client, isNotNull);
|
|
expect(connection.client!.rootChannel, isNotNull);
|
|
|
|
// Verify we can send a message
|
|
await connection.sendChannelMessage('Integration test message');
|
|
});
|
|
|
|
test('Should join different channels', () async {
|
|
// Setup test expectations
|
|
final connectionCompleter = Completer<bool>();
|
|
|
|
// Listen for connection state changes
|
|
connection.connectionState.listen((isConnected) {
|
|
if (isConnected && !connectionCompleter.isCompleted) {
|
|
connectionCompleter.complete(true);
|
|
}
|
|
});
|
|
|
|
// Connect to server
|
|
await connection.connect();
|
|
|
|
// Wait for connection
|
|
expect(await connectionCompleter.future.timeout(Duration(seconds: 10), onTimeout: () => false), isTrue);
|
|
|
|
expect(connection.client, isNotNull);
|
|
final channels = connection.client?.getChannels();
|
|
print(channels?.values);
|
|
expect(channels, isNotNull);
|
|
expect(channels?.length, greaterThan(1));
|
|
|
|
// Test joining the Music channel
|
|
await connection.joinChannel('Music');
|
|
expect(connection.currentChannel?.name, equals('Music'));
|
|
|
|
// Test joining a subchannel
|
|
await connection.joinChannel('Subroom1');
|
|
expect(connection.currentChannel?.name, equals('Subroom1'));
|
|
|
|
// Test joining another top-level channel
|
|
await connection.joinChannel('General');
|
|
expect(connection.currentChannel?.name, equals('General'));
|
|
|
|
// Test joining the Gaming channel
|
|
await connection.joinChannel('Gaming');
|
|
expect(connection.currentChannel?.name, equals('Gaming'));
|
|
|
|
// Test joining the root channel
|
|
await connection.joinChannel('');
|
|
expect(connection.currentChannel?.name, equals('Root'));
|
|
});
|
|
|
|
test('Should handle authentication failure', () async {
|
|
// Create a connection with incorrect password
|
|
final badConnection = MumbleConnection(
|
|
MumbleConfig(
|
|
server: config.mumble.server,
|
|
port: config.mumble.port,
|
|
username: config.mumble.username,
|
|
password: 'wrongpassword',
|
|
channel: config.mumble.channel,
|
|
),
|
|
);
|
|
|
|
// Try to connect with wrong password
|
|
try {
|
|
await badConnection.connect();
|
|
fail('Should have failed to connect with wrong password');
|
|
} catch (e) {
|
|
// Expected failure
|
|
expect(badConnection.isConnected, isFalse);
|
|
} finally {
|
|
// Clean up
|
|
await badConnection.disconnect();
|
|
}
|
|
});
|
|
|
|
// test('Should allow multiple bots to connect simultaneously', () async {
|
|
// // Load additional config
|
|
// final adminConfig = AppConfig.fromFile('${rootPath}/test/fixtures/test_configs/admin_config.json');
|
|
// final userConfig = AppConfig.fromFile('${rootPath}/test/fixtures/test_configs/user_config.json');
|
|
|
|
// // Create additional connections
|
|
// final adminConnection = MumbleConnection(adminConfig.mumble);
|
|
// final userConnection = MumbleConnection(userConfig.mumble);
|
|
|
|
// try {
|
|
// // Connect all bots
|
|
// await Future.wait([adminConnection.connect(), userConnection.connect()]);
|
|
|
|
// // Verify all connections are established
|
|
// expect(adminConnection.isConnected, isTrue);
|
|
// expect(userConnection.isConnected, isTrue);
|
|
|
|
// // Test sending messages from different bots
|
|
// await adminConnection.sendChannelMessage('Admin bot test message');
|
|
// await userConnection.sendChannelMessage('User bot test message');
|
|
|
|
// // Test joining different channels with each bot
|
|
// await adminConnection.joinChannel('Gaming');
|
|
// await userConnection.joinChannel('General');
|
|
|
|
// expect(adminConnection.currentChannel?.name, equals('Gaming'));
|
|
// expect(userConnection.currentChannel?.name, equals('General'));
|
|
// } finally {
|
|
// // Clean up
|
|
// await adminConnection.disconnect();
|
|
// await userConnection.disconnect();
|
|
// }
|
|
// });
|
|
|
|
test('Should handle reconnection', () async {
|
|
// Setup test expectations
|
|
final connectionCompleter = Completer<bool>();
|
|
final reconnectionCompleter = Completer<bool>();
|
|
var initiallyConnected = false;
|
|
|
|
// Listen for connection state changes
|
|
connection.connectionState.listen((isConnected) {
|
|
if (isConnected && !initiallyConnected) {
|
|
initiallyConnected = true;
|
|
connectionCompleter.complete(true);
|
|
} else if (isConnected && initiallyConnected && !reconnectionCompleter.isCompleted) {
|
|
reconnectionCompleter.complete(true);
|
|
}
|
|
});
|
|
|
|
// Connect initially
|
|
await connection.connect();
|
|
|
|
// Wait for initial connection
|
|
expect(
|
|
await connectionCompleter.future.timeout(Duration(seconds: 10), onTimeout: () => false),
|
|
isTrue,
|
|
reason: 'Failed to connect to Mumble server initially',
|
|
);
|
|
|
|
// Restart the Mumble server
|
|
print('Restarting Mumble server container to test reconnection...');
|
|
final restartProcess = await Process.run(
|
|
'docker-compose',
|
|
['-f', 'test/integration/docker-compose.test.yml', 'restart', 'mumble'],
|
|
workingDirectory: rootPath
|
|
);
|
|
|
|
if (restartProcess.exitCode != 0) {
|
|
fail('Failed to restart Mumble server: ${restartProcess.stderr}');
|
|
}
|
|
|
|
// Wait for reconnection
|
|
await expectLater(
|
|
reconnectionCompleter.future.timeout(Duration(seconds: 20), onTimeout: () => false),
|
|
isTrue,
|
|
reason: 'Failed to reconnect to Mumble server after restart',
|
|
);
|
|
|
|
// Verify connection is re-established
|
|
expect(connection.isConnected, isTrue);
|
|
});
|
|
}
|