mcp-adb/tests/test_devices.py
Ryan Malloy 3614ba8f8f Replace dict returns with typed Pydantic response models across all 65 tools
Every tool now returns a structured BaseModel instead of dict[str, Any],
giving callers attribute access, IDE autocomplete, and schema validation.
Adds ~30 model classes to models.py and updates all test assertions.
2026-02-11 03:57:25 -07:00

183 lines
6.6 KiB
Python

"""Tests for devices mixin (list, use, current, info, reboot, logcat)."""
import pytest
from tests.conftest import fail, ok
class TestDevicesList:
async def test_parse_devices(self, server):
server.run_adb.return_value = ok(
stdout=(
"List of devices attached\n"
"ABC123\tdevice\tmodel:Pixel_6 product:oriole\n"
"10.20.0.25:5555\tdevice\tmodel:K2401 product:K2401\n"
)
)
devices = await server.devices_list()
assert len(devices) == 2
assert devices[0].device_id == "ABC123"
assert devices[0].model == "Pixel_6"
assert devices[1].device_id == "10.20.0.25:5555"
async def test_empty(self, server):
server.run_adb.return_value = ok(stdout="List of devices attached\n")
devices = await server.devices_list()
assert len(devices) == 0
async def test_failure(self, server):
server.run_adb.return_value = fail("adb not found")
devices = await server.devices_list()
assert len(devices) == 0
class TestDevicesUse:
async def test_select_device(self, server):
server.run_adb.return_value = ok(
stdout="List of devices attached\nABC123\tdevice\n"
)
result = await server.devices_use("ABC123")
assert result.success is True
assert server.get_current_device() == "ABC123"
async def test_not_found(self, server):
server.run_adb.return_value = ok(
stdout="List of devices attached\nOTHER\tdevice\n"
)
result = await server.devices_use("MISSING")
assert result.success is False
assert "not found" in result.error
async def test_offline_device(self, server):
server.run_adb.return_value = ok(
stdout="List of devices attached\nABC123\toffline\n"
)
result = await server.devices_use("ABC123")
assert result.success is False
assert "offline" in result.error
class TestDevicesCurrent:
async def test_no_device_set(self, server):
server.run_adb.return_value = ok(stdout="List of devices attached\n")
result = await server.devices_current()
assert result.device is None
async def test_auto_detect_single(self, server):
server.run_adb.return_value = ok(
stdout="List of devices attached\nABC123\tdevice\n"
)
result = await server.devices_current()
assert result.available is not None
async def test_device_set(self, server):
# Pre-populate cache and set device
server.run_adb.return_value = ok(
stdout="List of devices attached\nABC123\tdevice\tmodel:Pixel\n"
)
await server.devices_list()
server.set_current_device("ABC123")
result = await server.devices_current()
# device is a dict from model_dump()
assert result.device["device_id"] == "ABC123"
class TestDeviceInfo:
async def test_full_info(self, server):
battery = (
"Current Battery Service state:\n level: 85\n status: 2\n plugged: 2"
)
df_out = (
"Filesystem 1K-blocks Used Available\n"
"/data 64000000 32000000 32000000"
)
server.run_shell_args.side_effect = [
ok(stdout=battery),
ok(stdout="10: wlan0 inet 192.168.1.100/24"),
ok(stdout="mWifiInfo SSID: MyNetwork, BSSID: ..."),
ok(stdout=df_out),
]
server.get_device_property.side_effect = lambda p, d=None: {
"ro.build.version.release": "14",
"ro.build.version.sdk": "34",
"ro.product.model": "Pixel 6",
"ro.product.manufacturer": "Google",
"ro.product.device": "oriole",
}.get(p)
result = await server.device_info()
assert result.success is True
assert result.battery["level"] == 85
assert result.ip_address == "192.168.1.100"
assert result.wifi_ssid == "MyNetwork"
assert result.model == "Pixel 6"
async def test_device_offline(self, server):
server.run_shell_args.return_value = fail("device offline")
result = await server.device_info()
assert result.success is False
class TestDeviceReboot:
@pytest.mark.usefixtures("_dev_mode")
async def test_reboot(self, server, ctx):
ctx.set_elicit("accept", "Yes, reboot now")
server.run_adb.return_value = ok()
result = await server.device_reboot(ctx)
assert result.success is True
assert result.mode == "normal"
@pytest.mark.usefixtures("_dev_mode")
async def test_reboot_recovery(self, server, ctx):
ctx.set_elicit("accept", "Yes, reboot now")
server.run_adb.return_value = ok()
result = await server.device_reboot(ctx, mode="recovery")
assert result.mode == "recovery"
@pytest.mark.usefixtures("_dev_mode")
async def test_cancelled(self, server, ctx):
ctx.set_elicit("accept", "Cancel")
result = await server.device_reboot(ctx)
assert result.success is False
assert result.cancelled is True
@pytest.mark.usefixtures("_no_dev_mode")
async def test_requires_dev_mode(self, server, ctx):
result = await server.device_reboot(ctx)
assert result.success is False
class TestLogcat:
@pytest.mark.usefixtures("_dev_mode")
async def test_capture(self, server):
logline = "01-01 00:00:00.000 I/TAG: message"
server.run_shell_args.return_value = ok(stdout=logline)
result = await server.logcat_capture()
assert result.success is True
assert result.output.startswith("01-01")
@pytest.mark.usefixtures("_dev_mode")
async def test_with_filter(self, server):
server.run_shell_args.return_value = ok(stdout="filtered output")
result = await server.logcat_capture(filter_spec="MyApp:D *:S")
assert result.filter == "MyApp:D *:S"
@pytest.mark.usefixtures("_dev_mode")
async def test_clear_first(self, server):
server.run_shell_args.side_effect = [ok(), ok(stdout="fresh logs")]
result = await server.logcat_capture(clear_first=True)
assert result.success is True
assert server.run_shell_args.call_count == 2
@pytest.mark.usefixtures("_no_dev_mode")
async def test_requires_dev_mode(self, server):
result = await server.logcat_capture()
assert result.success is False
@pytest.mark.usefixtures("_dev_mode")
async def test_logcat_clear(self, server):
server.run_shell_args.return_value = ok()
result = await server.logcat_clear()
assert result.success is True
assert result.action == "logcat_clear"