import os
import json
import pytest
import asyncio
from unittest.mock import patch, MagicMock, AsyncMock
# 导入被测试的模块
from phone_mcp.core import check_device_connection, run_command
from phone_mcp.tools.call import call_number, end_call
from phone_mcp.tools.messaging import (
send_text_message,
receive_text_messages,
get_raw_messages,
)
# 辅助函数,将同步结果转换为异步结果
def async_return(result):
"""将同步结果包装为异步结果的辅助函数"""
loop = asyncio.get_event_loop_policy().get_event_loop()
f = loop.create_future()
f.set_result(result)
return f
# 固定装置:模拟ADB命令执行
@pytest.fixture
def adb_mock():
"""模拟ADB命令执行的装置,可控制命令执行结果"""
with patch("phone_mcp.core.run_command", new_callable=AsyncMock) as mock_run:
yield mock_run
@pytest.mark.asyncio
class TestDeviceConnection:
"""测试设备连接检测功能"""
async def test_check_device_connection_connected(self, adb_mock):
"""测试设备已连接的情况"""
# 设置模拟的命令执行返回值
adb_mock.return_value = (True, "List of devices attached\nXXXXXXXX\tdevice")
# 执行被测试的函数
result = await check_device_connection()
# 验证结果
assert "connected and ready" in result
adb_mock.assert_called_with("adb devices")
async def test_check_device_connection_not_connected(self, adb_mock):
"""测试没有设备连接的情况"""
# 设置模拟的命令执行返回值,模拟自动重试行为
# 由于check_device_connection中有重试逻辑,这里需要多次设置返回值
adb_mock.side_effect = [
(True, "List of devices attached\n"), # 首次调用
(True, "Success"), # kill-server
(True, "Success"), # start-server
(True, "List of devices attached\n"), # 第二次调用
(True, "List of devices attached\n"), # 第三次调用
(True, "List of devices attached\n"), # 第四次调用
]
# 执行被测试的函数
result = await check_device_connection()
# 验证结果
assert "No device found" in result
assert "connected and ready" not in result
async def test_check_device_connection_failure(self, adb_mock):
"""测试命令执行失败的情况"""
# 设置模拟的命令执行返回值
adb_mock.return_value = (False, "Command failed: adb not found")
# 执行被测试的函数
result = await check_device_connection()
# 验证结果
assert "Failed to check device connection" in result
assert "adb not found" in result
@pytest.mark.asyncio
class TestPhoneFunctionality:
"""测试手机的基本功能,如拨打电话、发送短信等"""
async def test_call_number_success(self, adb_mock):
"""测试成功拨打电话的情况"""
# 直接修改phone_mcp.tools.call模块中run_command的引用
with patch(
"phone_mcp.tools.call.run_command", new_callable=AsyncMock
) as mock_call:
# 设置模拟的命令执行返回值
mock_call.return_value = (True, "Success")
# 执行被测试的函数
phone_number = "10086"
result = await call_number(phone_number)
# 验证结果
assert "+86" + phone_number in result # 注意:函数会自动添加+86前缀
assert "Calling" in result
# 验证被调用
assert mock_call.called
async def test_call_number_failure(self, adb_mock):
"""测试拨打电话失败的情况"""
# 直接修改phone_mcp.tools.call模块中run_command的引用
with patch(
"phone_mcp.tools.call.run_command", new_callable=AsyncMock
) as mock_call:
# 设置模拟的命令执行返回值
mock_call.return_value = (False, "Failed to execute command")
# 执行被测试的函数
result = await call_number("10086")
# 验证结果
assert "Failed to initiate call" in result
# 验证被调用
assert mock_call.called
async def test_end_call_success(self, adb_mock):
"""测试成功结束通话的情况"""
# 直接修改phone_mcp.tools.call模块中run_command的引用
with patch(
"phone_mcp.tools.call.run_command", new_callable=AsyncMock
) as mock_call:
# 设置模拟的命令执行返回值
mock_call.return_value = (True, "Success")
# 执行被测试的函数
result = await end_call()
# 验证结果
assert "Call ended successfully" in result
# 验证被调用的参数
mock_call.assert_called_with("adb shell input keyevent KEYCODE_ENDCALL")
async def test_end_call_failure(self, adb_mock):
"""测试结束通话失败的情况"""
# 直接修改phone_mcp.tools.call模块中run_command的引用
with patch(
"phone_mcp.tools.call.run_command", new_callable=AsyncMock
) as mock_call:
# 设置模拟的命令执行返回值
mock_call.return_value = (False, "Failed to execute command")
# 执行被测试的函数
result = await end_call()
# 验证结果
assert "Failed to end call" in result
# 验证被调用的参数
mock_call.assert_called_with("adb shell input keyevent KEYCODE_ENDCALL")
async def test_send_text_message_success(self, adb_mock):
"""测试成功发送短信的情况"""
# 直接修改phone_mcp.tools.messaging模块中run_command的引用
with patch(
"phone_mcp.tools.messaging.run_command", new_callable=AsyncMock
) as mock_msg:
# 模拟多个命令调用的成功情况
mock_msg.side_effect = [
(True, "Success"), # 打开短信app
(True, "Success"), # 按键事件1
(True, "Success"), # 按键事件2
(True, "Success"), # 退出app
]
# 模拟sleep函数,避免实际等待
with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
# 执行被测试的函数
phone_number = "10086"
message = "测试短信"
result = await send_text_message(phone_number, message)
# 验证结果
assert "+86" + phone_number in result # 注意:函数会自动添加+86前缀
assert "sent" in result
# 验证被调用至少一次
assert mock_msg.call_count > 0
async def test_send_text_message_failure(self, adb_mock):
"""测试发送短信失败的情况 - 第二步失败"""
# 直接修改phone_mcp.tools.messaging模块中run_command的引用
with patch(
"phone_mcp.tools.messaging.run_command", new_callable=AsyncMock
) as mock_msg:
# 模拟第一次调用成功,第二次调用失败
mock_msg.side_effect = [
(True, "Success"), # 打开短信app成功
(False, "Failed"), # 按键事件1失败
]
# 模拟sleep函数,避免实际等待
with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
# 执行被测试的函数
result = await send_text_message("10086", "测试短信")
# 验证结果
assert "Failed to navigate to send button" in result
async def test_receive_text_messages_success(self, adb_mock):
"""测试成功获取短信的情况"""
# 由于receive_text_messages会调用check_device_connection,
# 我们需要先模拟core模块中的check_device_connection
with patch(
"phone_mcp.core.check_device_connection", new_callable=AsyncMock
) as mock_check:
# 模拟设备连接正常
mock_check.return_value = "Device is connected and ready."
# 修改phone_mcp.tools.messaging模块中run_command的引用
with patch(
"phone_mcp.tools.messaging.run_command", new_callable=AsyncMock
) as mock_msg:
# 模拟短信查询返回正确的短信内容
mock_msg.return_value = (
True,
"""Row: 0 address=10086, body=你好,这是测试短信, date=1628151234567
Row: 1 address=13900139000, body=这是另一条测试短信, date=1628151234000""",
)
# 执行被测试的函数
result = await receive_text_messages(limit=2)
# 验证结果是否包含短信内容
assert isinstance(result, str)
# 尝试解析JSON结果
try:
messages = json.loads(result)
assert isinstance(messages, list)
assert len(messages) <= 2 # 不应超过限制
# 验证短信内容
if messages:
first_msg = messages[0]
assert any(key in first_msg for key in ["address", "from"])
assert any(key in first_msg for key in ["body", "text"])
except json.JSONDecodeError:
# 如果不是JSON格式,应该包含短信相关信息
assert "SMS" in result or "短信" in result
async def test_receive_text_messages_no_device(self, adb_mock):
"""测试没有设备连接时获取短信的情况"""
# 模拟设备连接检查失败
with patch(
"phone_mcp.core.check_device_connection", new_callable=AsyncMock
) as mock_check:
# 返回设备未连接消息
mock_check.return_value = "No device found. Please connect a device and ensure USB debugging is enabled."
# 执行被测试的函数
result = await receive_text_messages()
# 验证结果
assert "No device found" in result
async def test_get_raw_messages_success(self, adb_mock):
"""测试成功获取原始短信的情况"""
# 由于get_raw_messages会调用check_device_connection,
# 我们需要先模拟设备连接检查,再模拟短信内容查询
with patch(
"phone_mcp.core.check_device_connection", new_callable=AsyncMock
) as mock_check:
# 模拟设备连接正常
mock_check.return_value = "Device is connected and ready."
# 修改phone_mcp.tools.messaging模块中run_command的引用
with patch(
"phone_mcp.tools.messaging.run_command", new_callable=AsyncMock
) as mock_msg:
# 模拟短信查询返回正确的短信内容
mock_msg.return_value = (
True,
"""Row: 0 address=10086, body=Hello, date=1628151234567
Row: 1 address=13900139000, body=世界, date=1628151234000""",
)
# 执行被测试的函数
result = await get_raw_messages(limit=2)
# 验证结果包含关键信息
assert isinstance(result, str)
assert any(text in result for text in ["Found", "SMS", "短信", "消息"])
assert any(
text in result
for text in ["Hello", "世界", "10086", "13900139000"]
)
async def test_get_raw_messages_no_messages(self, adb_mock):
"""测试没有短信时的情况"""
# 由于get_raw_messages会调用check_device_connection,
# 我们需要先模拟设备连接检查,再模拟短信内容查询
with patch(
"phone_mcp.core.check_device_connection", new_callable=AsyncMock
) as mock_check:
# 模拟设备连接正常
mock_check.return_value = "Device is connected and ready."
# 修改phone_mcp.tools.messaging模块中run_command的引用
with patch(
"phone_mcp.tools.messaging.run_command", new_callable=AsyncMock
) as mock_msg:
# 模拟短信查询返回空结果 - 无Row:标记表示没有短信
mock_msg.return_value = (True, "No SMS found")
# 执行被测试的函数
result = await get_raw_messages()
# 验证结果
assert "Unable to retrieve" in result or "No SMS" in result