""" FridaManager 单元测试 (H30) 使用 Mock 模拟 frida 库,不需要真机/frida-server。 测试 FridaManager 的生命周期管理、状态管理和错误处理。 """ import sys import os import unittest from unittest.mock import MagicMock, patch, PropertyMock sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "agent")) class TestFridaManagerInit(unittest.TestCase): """测试 FridaManager 初始化和属性""" def test_initial_state(self): from hook.frida_manager import FridaManager mgr = FridaManager(device_serial="abc123") self.assertEqual(mgr.device_serial, "abc123") self.assertFalse(mgr.connected) self.assertIsNone(mgr.rpc) self.assertFalse(mgr._running) def test_default_script_path(self): from hook.frida_manager import FridaManager, DEFAULT_SCRIPT mgr = FridaManager() self.assertEqual(mgr.script_path, DEFAULT_SCRIPT) self.assertTrue(mgr.script_path.endswith("wechat_hook_v2.js")) def test_get_status_disconnected(self): from hook.frida_manager import FridaManager mgr = FridaManager(device_serial="test_serial") status = mgr.get_status() self.assertFalse(status["connected"]) self.assertIsNone(status["device"]) self.assertEqual(status["device_serial"], "test_serial") self.assertFalse(status["running"]) class TestFridaManagerStartNoFrida(unittest.TestCase): """frida 库未安装时应优雅失败""" @patch.dict("sys.modules", {"frida": None}) def test_start_without_frida_returns_false(self): from hook.frida_manager import FridaManager mgr = FridaManager() with patch("builtins.__import__", side_effect=ImportError("No module named 'frida'")): result = mgr.start() self.assertFalse(result) class TestFridaManagerWithMockFrida(unittest.TestCase): """模拟 frida 库测试核心流程""" def setUp(self): self.mock_frida = MagicMock() self.mock_device = MagicMock() self.mock_device.name = "Test Xiaomi" self.mock_session = MagicMock() self.mock_script = MagicMock() self.mock_rpc = MagicMock() self.mock_rpc.ping.return_value = "pong from wechat_hook_v2.1" self.mock_device.enumerate_processes.return_value = [ MagicMock(name="com.tencent.mm", identifier="com.tencent.mm", pid=12345) ] self.mock_device.attach.return_value = self.mock_session self.mock_session.create_script.return_value = self.mock_script self.mock_script.exports_sync = self.mock_rpc @patch("hook.frida_manager.frida", create=True) def test_call_rpc_disconnected(self, mock_frida_module): from hook.frida_manager import FridaManager mgr = FridaManager() result = mgr.call_rpc("ping") self.assertFalse(result["success"]) self.assertIn("未连接", result["error"]) def test_call_rpc_unknown_method(self): from hook.frida_manager import FridaManager mgr = FridaManager() mgr._session = self.mock_session mgr._script = self.mock_script mgr._rpc = self.mock_rpc self.mock_rpc.nonexistent = None delattr(self.mock_rpc, "nonexistent") result = mgr.call_rpc("nonexistent") self.assertFalse(result["success"]) def test_call_rpc_success(self): from hook.frida_manager import FridaManager mgr = FridaManager() mgr._session = self.mock_session mgr._script = self.mock_script mgr._rpc = MagicMock() mgr._rpc.send_message.return_value = {"success": True, "message_id": "m123"} result = mgr.call_rpc("sendMessage", {"to_id": "wxid_abc", "content": "hi"}) self.assertTrue(result["success"]) self.assertEqual(result["message_id"], "m123") def test_call_rpc_exception(self): from hook.frida_manager import FridaManager mgr = FridaManager() mgr._session = self.mock_session mgr._script = self.mock_script mgr._rpc = MagicMock() mgr._rpc.send_message.side_effect = Exception("进程已退出") result = mgr.call_rpc("sendMessage", {}) self.assertFalse(result["success"]) self.assertIn("进程已退出", result["error"]) def test_stop_cleans_up(self): from hook.frida_manager import FridaManager mgr = FridaManager() mgr._session = self.mock_session mgr._script = self.mock_script mgr._running = True mgr.stop() self.assertFalse(mgr._running) self.assertIsNone(mgr._script) self.assertIsNone(mgr._session) def test_on_message_log(self): from hook.frida_manager import FridaManager mgr = FridaManager() message = {"type": "send", "payload": {"type": "log", "level": "info", "tag": "test", "message": "hello"}} mgr._on_message(message, None) def test_on_message_hook_event(self): from hook.frida_manager import FridaManager events = [] mgr = FridaManager(on_event=lambda e: events.append(e)) message = {"type": "send", "payload": {"type": "hook_event", "event_type": "message_received", "payload": {}}} mgr._on_message(message, None) self.assertEqual(len(events), 1) self.assertEqual(events[0]["event_type"], "message_received") def test_on_message_error(self): from hook.frida_manager import FridaManager mgr = FridaManager() message = {"type": "error", "description": "Script destroyed"} mgr._on_message(message, None) class TestEventReporter(unittest.TestCase): """EventReporter 单元测试""" def test_buffer_events_without_send_fn(self): from hook.event_reporter import EventReporter reporter = EventReporter() reporter.on_hook_event({"event_type": "test", "payload": {"a": 1}}) reporter.on_hook_event({"event_type": "test2", "payload": {"b": 2}}) stats = reporter.get_stats() self.assertEqual(stats["total"], 2) self.assertEqual(stats["buffered"], 2) self.assertEqual(stats["sent"], 0) def test_send_events_with_fn(self): from hook.event_reporter import EventReporter sent = [] reporter = EventReporter(send_fn=lambda e: sent.append(e)) reporter.on_hook_event({"event_type": "msg", "payload": {}}) self.assertEqual(len(sent), 1) stats = reporter.get_stats() self.assertEqual(stats["sent"], 1) def test_flush_buffer_on_set_send_fn(self): from hook.event_reporter import EventReporter reporter = EventReporter() reporter.on_hook_event({"event_type": "buffered1", "payload": {}}) reporter.on_hook_event({"event_type": "buffered2", "payload": {}}) sent = [] reporter.set_send_fn(lambda e: sent.append(e)) self.assertEqual(len(sent), 2) def test_get_buffered_events(self): from hook.event_reporter import EventReporter reporter = EventReporter() for i in range(5): reporter.on_hook_event({"event_type": f"ev_{i}", "payload": {}}) events = reporter.get_buffered_events(limit=3) self.assertEqual(len(events), 3) def test_send_failure_buffers(self): from hook.event_reporter import EventReporter def failing_send(e): raise ConnectionError("offline") reporter = EventReporter(send_fn=failing_send) reporter.on_hook_event({"event_type": "fail", "payload": {}}) stats = reporter.get_stats() self.assertEqual(stats["errors"], 1) self.assertEqual(stats["buffered"], 1) if __name__ == "__main__": unittest.main()