"""Local-only cross-machine capture/navigation checks; all desktop input mocked.""" import unittest from unittest import mock import numpy as np from PIL import Image import wechat_bot as module from wechat_bot import WeChatBot, WindowCaptureError from test_message_navigation import navigation_bot, narrow_surface, labels def frame(width=400,height=300): pixels=np.full((height,width,4),245,dtype=np.uint8) pixels[:,:,3]=255 pixels[:,:58,:3]=(240,220,205) pixels[70:92,8:48,:3]=(235,120,35) pixels[100:130,80:160,:3]=25 return pixels class WindowCaptureTests(unittest.TestCase): def setUp(self): self.rect=(100,200,500,500) self.pixels=frame() for patcher in ( mock.patch.object(module.win32gui,"GetWindowRect",return_value=self.rect), mock.patch.object(module,"window_dpi_virtualization_factor",return_value=1), ): patcher.start() self.addCleanup(patcher.stop) def test_valid_gpu_frame_uses_no_desktop_capture(self): with mock.patch.object(module,"_capture_window_bitmap",return_value=self.pixels), mock.patch.object(module,"_capture_visible_window") as fallback: result=module.capture_window_region(100,58,90,160,80) np.testing.assert_array_equal(result,self.pixels[90:170,58:218]) fallback.assert_not_called() def test_black_gpu_frame_uses_visible_fallback(self): with mock.patch.object(module,"_capture_window_bitmap",return_value=np.zeros_like(self.pixels)), mock.patch.object(module,"_capture_visible_window",return_value=self.pixels) as fallback: result=module.capture_window_region(100,58,90,160,80) np.testing.assert_array_equal(result,self.pixels[90:170,58:218]) fallback.assert_called_once_with(100,self.rect) def test_gpu_api_failure_uses_visible_fallback(self): with mock.patch.object(module,"_capture_window_bitmap",side_effect=RuntimeError("failed")), mock.patch.object(module,"_capture_visible_window",return_value=self.pixels): np.testing.assert_array_equal(module.capture_window_region(100,0,0,400,300),self.pixels) def test_both_blank_sources_fail_instead_of_zero_unread(self): with mock.patch.object(module,"_capture_window_bitmap",return_value=np.zeros_like(self.pixels)), mock.patch.object(module,"_capture_visible_window",return_value=np.zeros_like(self.pixels)): with self.assertRaises(WindowCaptureError): module.capture_window_region(100,0,0,400,300) def test_dpi_bitmap_matches_physical_mouse_coordinates(self): high=np.repeat(np.repeat(self.pixels,2,axis=0),2,axis=1) high[:,:,3]=0 # Real GDI bitmaps frequently contain unused zero alpha. with mock.patch.object(module,"window_dpi_virtualization_factor",return_value=2), mock.patch.object(module,"_capture_window_bitmap",return_value=high) as bitmap: result=module.capture_window_region(100,0,0,400,300) bitmap.assert_called_once_with(100,800,600) self.assertEqual(result.shape,self.pixels[:,:,:3].shape) self.assertLess(float(np.abs(result.astype(float)-self.pixels[:,:,:3]).mean()),2) def test_window_move_invalidates_frame(self): with mock.patch.object(module.win32gui,"GetWindowRect",side_effect=[self.rect,(200,200,600,500)]), mock.patch.object(module,"_capture_window_bitmap",return_value=self.pixels): with self.assertRaises(WindowCaptureError): module.capture_window_region(100,0,0,400,300) def test_uniform_color_fails_but_dark_ui_is_valid(self): for value in (0,32,240,255): self.assertFalse(module.frame_has_content(np.full((400,600,3),value,dtype=np.uint8))) solid=np.empty((400,600,3),dtype=np.uint8) solid[:]=(240,220,205) self.assertFalse(module.frame_has_content(solid)) dark=np.full((400,600,3),32,dtype=np.uint8) dark[:,:60]=64 self.assertTrue(module.frame_has_content(dark)) def test_outside_region_is_rejected(self): with mock.patch.object(module,"_capture_window_bitmap",return_value=self.pixels): with self.assertRaises(WindowCaptureError): module.capture_window_region(100,-10,0,100,100) def test_foreground_and_occlusion_are_checked(self): with mock.patch.object(module.win32gui,"GetForegroundWindow",return_value=100) as foreground, mock.patch.object(module.win32gui,"IsWindowVisible",return_value=True), mock.patch.object(module.win32gui,"IsIconic",return_value=False), mock.patch.object(module.win32gui,"WindowFromPoint",return_value=100) as hit, mock.patch.object(module.win32gui,"IsChild",return_value=False): self.assertTrue(module._window_screen_capture_allowed(100,self.rect)) hit.return_value=200 self.assertFalse(module._window_screen_capture_allowed(100,self.rect)) hit.return_value=100 foreground.return_value=200 self.assertFalse(module._window_screen_capture_allowed(100,self.rect)) def test_visible_capture_uses_window_bbox_and_checks_after_capture(self): with mock.patch.object(module,"_window_screen_capture_allowed",side_effect=[True,True]) as allowed, mock.patch("PIL.ImageGrab.grab",return_value=Image.fromarray(self.pixels[:,:,:3])) as grab: result=module._capture_visible_window(100,self.rect) self.assertEqual(result.shape,(300,400,3)) self.assertEqual(allowed.call_count,2) grab.assert_called_once_with(bbox=self.rect,all_screens=True) with mock.patch.object(module,"_window_screen_capture_allowed",side_effect=[True,False]), mock.patch("PIL.ImageGrab.grab",return_value=Image.fromarray(self.pixels[:,:,:3])): with self.assertRaises(WindowCaptureError): module._capture_visible_window(100,self.rect) def test_failed_printwindow_releases_gdi_handles(self): dc,memory,bitmap=mock.Mock(),mock.Mock(),mock.Mock() dc.CreateCompatibleDC.return_value=memory with mock.patch.object(module.win32gui,"GetWindowDC",return_value=12), mock.patch.object(module.win32ui,"CreateDCFromHandle",return_value=dc), mock.patch.object(module.win32ui,"CreateBitmap",return_value=bitmap), mock.patch.object(module.ctypes.windll.user32,"PrintWindow",return_value=0), mock.patch.object(module.win32gui,"DeleteObject") as delete, mock.patch.object(module.win32gui,"ReleaseDC") as release: with self.assertRaises(WindowCaptureError): module._capture_window_bitmap(100,400,300) delete.assert_called_once_with(bitmap.GetHandle()) release.assert_called_once_with(100,12) dc.DeleteDC.assert_called_once() memory.DeleteDC.assert_called_once() class OtherComputerNavigationTests(unittest.TestCase): def test_capture_to_unread_click_across_scales_and_capture_backends(self): from packaging_checks import check_cross_machine_capture check_cross_machine_capture() def test_gray_dark_sidebars_at_multiple_scales(self): for scale in (1,1.25,1.5,2): for color in (190,32): with self.subTest(scale=scale,color=color): bot=navigation_bot(scale) surface=narrow_surface(scale) surface[:,:int(58*scale),:]=color width,confidence=module.infer_navigation_width(surface,scale) self.assertAlmostEqual(width,58*scale,delta=2) self.assertGreaterEqual(confidence,.85) self.assertTrue(bot._message_nav_band(surface)[2]) def test_blue_workbench_is_not_messages_without_label(self): bot=navigation_bot(1) bot._strict_visual_actions=True bot._name_reader_instance.read_layout.return_value=[] surface=narrow_surface(1) surface[65:105,2:58,:]=(238,126,36) self.assertFalse(bot._message_nav_selected(surface)) def test_same_size_layout_change_forces_fresh_location(self): bot=navigation_bot() surface=narrow_surface() original=bot._message_nav_band(surface) moved=surface.copy() moved[210:220,20:50,:]=55 bot._name_reader_instance.read_layout.return_value=labels(message_y=230) self.assertNotEqual(bot._message_nav_band(moved),original) self.assertEqual(bot._name_reader_instance.read_layout.call_count,2) def test_failed_unread_lookup_retries(self): bot=navigation_bot() bot._locate_unread_group_band_by_text=mock.Mock(side_effect=[None,(400,440)]) surface=narrow_surface() self.assertIsNone(bot._unread_group_band(surface)) self.assertEqual(bot._unread_group_band(surface),(400,440)) def test_failed_frame_never_triggers_model_or_mouse(self): bot=WeChatBot.__new__(WeChatBot) bot._drop_stale_review_flags=mock.Mock() bot.wait_for_mouse_idle=mock.Mock(return_value=True) bot._dismiss_owned_blocking_window=mock.Mock(return_value=False) bot._ensure_visible=mock.Mock(return_value=True) bot._security_gate_visible=mock.Mock(return_value=False) bot._capture_full_window=mock.Mock(side_effect=WindowCaptureError("blank")) bot._recover_capture_failure=mock.Mock() bot._run_ai_page_guard=mock.Mock() with mock.patch.object(module.pyautogui,"click") as click: bot._poll_once() bot._recover_capture_failure.assert_called_once_with("blank") bot._run_ai_page_guard.assert_not_called() click.assert_not_called() def test_alive_empty_window_reselects_main_and_preserves_tasks(self): bot=WeChatBot.__new__(WeChatBot) bot.hwnd=100 bot._active_session_fp=b"old" bot._pending_reply_sessions={"customer":{"stage":"queued"}} bot._pending_scan_progress={"old":{}} bot.report_progress=mock.Mock() bot.connect=mock.Mock(return_value=True) with mock.patch.object(module,"find_wx_hwnd",return_value=200), mock.patch.object(module,"window_looks_rendered",return_value=True): bot._recover_capture_failure("blank") bot.connect.assert_called_once_with(activate=False) self.assertFalse(bot._window_ready) self.assertIsNone(bot._active_session_fp) self.assertEqual(bot._pending_scan_progress,{}) self.assertIn("customer",bot._pending_reply_sessions) if __name__ == "__main__": unittest.main()