test_device_selection.py•5.12 kB
"""Unit tests for device selection configuration and resolution."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict, List
import pytest
from frida_mcp.device_selection import DeviceSelector, create_config
# Constants avoid test magic values and ease future maintenance
LOCAL_DEVICE_ID = "local"
LOCAL_DEVICE_NAME = "Local Device"
LOCAL_DEVICE_TYPE = "local"
USB_DEVICE_ID = "usb"
USB_DEVICE_NAME = "USB Device"
USB_DEVICE_TYPE = "usb"
REMOTE_ADDRESS = "192.0.2.42:27042"
REMOTE_DEVICE_ID = f"tcp@{REMOTE_ADDRESS}"
REMOTE_DEVICE_NAME = "Remote Device"
REMOTE_DEVICE_TYPE = "remote"
REMOTE_ALIAS = "lab-remote"
FALLBACK_REMOTE_USB: List[str] = ["remote", "usb"]
class _FakeInvalidArgumentError(Exception):
"""Stub replacement for frida.InvalidArgumentError."""
class _FakeTransportError(Exception):
"""Stub replacement for frida.TransportError."""
@dataclass
class _FakeDevice:
"""Simple stand-in for a Frida device object."""
id: str
name: str
type: str
class _FakeDeviceManager:
"""Mimics the subset of the real device manager used in tests."""
def __init__(self, devices: Dict[str, _FakeDevice]):
self._devices = devices
def add_remote_device(self, address: str) -> _FakeDevice:
lookup_id = f"tcp@{address}"
try:
return self._devices[lookup_id]
except KeyError as exc: # pragma: no cover - defensive guard
raise _FakeInvalidArgumentError from exc
class _FakeFridaModule:
"""Minimal Frida substitute wired to predictable fake devices."""
InvalidArgumentError = _FakeInvalidArgumentError
TransportError = _FakeTransportError
def __init__(self):
self._local = _FakeDevice(LOCAL_DEVICE_ID, LOCAL_DEVICE_NAME, LOCAL_DEVICE_TYPE)
self._usb = _FakeDevice(USB_DEVICE_ID, USB_DEVICE_NAME, USB_DEVICE_TYPE)
self._remote = _FakeDevice(
REMOTE_DEVICE_ID, REMOTE_DEVICE_NAME, REMOTE_DEVICE_TYPE
)
self._devices: Dict[str, _FakeDevice] = {
self._local.id: self._local,
self._usb.id: self._usb,
self._remote.id: self._remote,
}
self._device_manager = _FakeDeviceManager(self._devices)
# API surface used by DeviceSelector
def get_device_manager(self) -> _FakeDeviceManager:
return self._device_manager
def enumerate_devices(self) -> List[_FakeDevice]:
return list(self._devices.values())
def get_device(self, device_id: str) -> _FakeDevice:
try:
return self._devices[device_id]
except KeyError as exc:
raise self.InvalidArgumentError from exc
def get_usb_device(self) -> _FakeDevice:
return self._usb
def get_local_device(self) -> _FakeDevice:
return self._local
def _clear_device_env(monkeypatch: pytest.MonkeyPatch) -> None:
"""Ensure environment-based defaults do not leak into tests."""
for key in (
"FRIDA_DEFAULT_DEVICE",
"FRIDA_DEFAULT_REMOTE",
"FRIDA_DEVICE_FALLBACKS",
):
monkeypatch.delenv(key, raising=False)
def test_create_config_applies_remote_defaults(monkeypatch: pytest.MonkeyPatch) -> None:
"""Configuration helper should set remote defaults when supplied."""
_clear_device_env(monkeypatch)
config = create_config(remote_address=REMOTE_ADDRESS, remote_alias=REMOTE_ALIAS)
assert config.default_device == "remote"
assert config.default_remote == REMOTE_ADDRESS
assert config.alias_to_address[REMOTE_ALIAS.lower()] == REMOTE_ADDRESS
def test_get_device_honors_explicit_local_mode(monkeypatch: pytest.MonkeyPatch) -> None:
"""Selector should choose the local device when mode is 'local'."""
_clear_device_env(monkeypatch)
fake_frida = _FakeFridaModule()
selector = DeviceSelector(
config=create_config(mode="local"),
frida_module=fake_frida,
device_manager=fake_frida.get_device_manager(),
)
device = selector.get_device()
assert device.id == LOCAL_DEVICE_ID
def test_get_device_resolves_remote_alias(monkeypatch: pytest.MonkeyPatch) -> None:
"""Alias lookups should resolve to the configured remote device."""
_clear_device_env(monkeypatch)
fake_frida = _FakeFridaModule()
selector = DeviceSelector(
config=create_config(remote_address=REMOTE_ADDRESS, remote_alias=REMOTE_ALIAS),
frida_module=fake_frida,
device_manager=fake_frida.get_device_manager(),
)
device = selector.get_device(REMOTE_ALIAS)
assert device.id == REMOTE_DEVICE_ID
def test_auto_select_uses_fallback_order(monkeypatch: pytest.MonkeyPatch) -> None:
"""Automatic selection should follow the configured fallback ordering."""
_clear_device_env(monkeypatch)
fake_frida = _FakeFridaModule()
selector = DeviceSelector(
config=create_config(fallback_priority=FALLBACK_REMOTE_USB),
frida_module=fake_frida,
device_manager=fake_frida.get_device_manager(),
)
device = selector.get_device()
assert device.id == REMOTE_DEVICE_ID