feat: 检测 Android 设备 PDD 安装状态 (#29)
This commit is contained in:
@@ -51,6 +51,9 @@ class SequenceAndroidDeviceService:
|
||||
self.restore_serials.append(serial)
|
||||
return self.search(is_cancelled)
|
||||
|
||||
def is_package_installed(self, _serial, _package_name):
|
||||
return True
|
||||
|
||||
|
||||
class SlowAndroidDeviceService:
|
||||
"""模拟较慢的 ADB 搜索。"""
|
||||
@@ -68,6 +71,9 @@ class SlowAndroidDeviceService:
|
||||
self.restore_serial = serial
|
||||
return self.search(is_cancelled)
|
||||
|
||||
def is_package_installed(self, _serial, _package_name):
|
||||
return True
|
||||
|
||||
|
||||
class ControlledWifiConversionService:
|
||||
"""模拟 USB 转 Wi-Fi 的进度、成功、失败和延迟。"""
|
||||
@@ -76,10 +82,18 @@ class ControlledWifiConversionService:
|
||||
self.delay = delay
|
||||
self.error = error
|
||||
self.call_count = 0
|
||||
self.search_count = 0
|
||||
|
||||
def search(self, _is_cancelled=None):
|
||||
self.search_count += 1
|
||||
return []
|
||||
|
||||
def restore_saved_device(self, _serial, is_cancelled=None):
|
||||
return self.search(is_cancelled)
|
||||
|
||||
def is_package_installed(self, _serial, _package_name):
|
||||
return True
|
||||
|
||||
def convert_usb_to_wifi(
|
||||
self,
|
||||
usb_serial,
|
||||
@@ -106,6 +120,34 @@ class ControlledWifiConversionService:
|
||||
)
|
||||
|
||||
|
||||
class ControlledPackageCheckService:
|
||||
"""控制 PDD 包检测结果,其他 ADB 行为返回空列表。"""
|
||||
|
||||
def __init__(self, installed=True, delay=0.0, error=None):
|
||||
self.installed = installed
|
||||
self.delay = delay
|
||||
self.error = error
|
||||
self.checked_serials = []
|
||||
self.search_count = 0
|
||||
|
||||
def search(self, _is_cancelled=None):
|
||||
self.search_count += 1
|
||||
return []
|
||||
|
||||
def restore_saved_device(self, _serial, _is_cancelled=None):
|
||||
return self.search(_is_cancelled)
|
||||
|
||||
def is_package_installed(self, serial, _package_name):
|
||||
self.checked_serials.append(serial)
|
||||
if self.delay:
|
||||
time.sleep(self.delay)
|
||||
if self.error is not None:
|
||||
raise self.error
|
||||
if isinstance(self.installed, dict):
|
||||
return self.installed.get(serial, False)
|
||||
return self.installed
|
||||
|
||||
|
||||
class ControlledSettingsRepository(SettingsRepository):
|
||||
"""控制 Android 设备设置写入速度和失败,其他设置保持正常。"""
|
||||
|
||||
@@ -399,19 +441,24 @@ class SettingsPageEventTest(unittest.TestCase):
|
||||
page.deleteLater()
|
||||
|
||||
def test_selected_android_device_is_saved_to_sqlite(self):
|
||||
service = ControlledPackageCheckService()
|
||||
page = SettingsPage(
|
||||
settings_repository=self.repository,
|
||||
admin_gateway=MockAdminGateway(),
|
||||
android_device_service=service,
|
||||
)
|
||||
page.set_android_devices(
|
||||
[AndroidDeviceRow("USB-001", "USB", "Pixel 8", "14")]
|
||||
)
|
||||
page.deviceTableModel.set_checked_serial("USB-001")
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_thread is None)
|
||||
|
||||
page.saveButton.click()
|
||||
self.assertFalse(page.saveButton.isEnabled())
|
||||
self._wait_until(
|
||||
lambda: page.eventBinder._android_setting_thread is None
|
||||
lambda: self.repository.get(SELECTED_ANDROID_SERIAL_KEY)
|
||||
== "USB-001"
|
||||
and page.eventBinder._android_setting_thread is None
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
@@ -670,13 +717,89 @@ class SettingsPageEventTest(unittest.TestCase):
|
||||
page.eventBinder.shutdown()
|
||||
page.deleteLater()
|
||||
|
||||
def test_selected_android_device_save_failure_keeps_old_configuration(self):
|
||||
repository = ControlledSettingsRepository(self.db_path)
|
||||
repository.set(SELECTED_ANDROID_SERIAL_KEY, "USB-OLD")
|
||||
repository.fail_selected_write = True
|
||||
def test_selected_device_shows_installed_pdd_package(self):
|
||||
service = ControlledPackageCheckService(installed=True)
|
||||
page = SettingsPage(
|
||||
settings_repository=repository,
|
||||
settings_repository=self.repository,
|
||||
admin_gateway=MockAdminGateway(),
|
||||
android_device_service=service,
|
||||
)
|
||||
page.set_android_devices([AndroidDeviceRow("USB-001", "USB")])
|
||||
|
||||
page.deviceTableModel.set_checked_serial("USB-001")
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_thread is None)
|
||||
|
||||
self.assertEqual(service.checked_serials, ["USB-001"])
|
||||
self.assertEqual(
|
||||
page.pddAppStatusLabel.text(),
|
||||
"com.xunmeng.pinduoduo(已安装)",
|
||||
)
|
||||
self.assertFalse(hasattr(page, "addressInput"))
|
||||
self.assertFalse(hasattr(page, "connectButton"))
|
||||
page.eventBinder.shutdown()
|
||||
page.deleteLater()
|
||||
|
||||
def test_save_rechecks_pdd_and_blocks_device_without_app(self):
|
||||
service = ControlledPackageCheckService(installed=False)
|
||||
page = SettingsPage(
|
||||
settings_repository=self.repository,
|
||||
admin_gateway=MockAdminGateway(),
|
||||
android_device_service=service,
|
||||
)
|
||||
page.set_android_devices([AndroidDeviceRow("USB-001", "USB")])
|
||||
page.deviceTableModel.set_checked_serial("USB-001")
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_thread is None)
|
||||
|
||||
page.saveButton.click()
|
||||
self._wait_until(
|
||||
lambda: len(service.checked_serials) == 2
|
||||
and page.eventBinder._pdd_check_thread is None
|
||||
)
|
||||
|
||||
self.assertIsNone(
|
||||
self.repository.get(SELECTED_ANDROID_SERIAL_KEY)
|
||||
)
|
||||
self.assertIn("未安装", page.pddAppStatusLabel.text())
|
||||
self.assertIn("保存失败", page.deviceStatusLabel.text())
|
||||
page.eventBinder.shutdown()
|
||||
page.deleteLater()
|
||||
|
||||
def test_save_blocks_when_pdd_check_fails(self):
|
||||
service = ControlledPackageCheckService(
|
||||
error=AndroidDeviceSearchError("device offline")
|
||||
)
|
||||
page = SettingsPage(
|
||||
settings_repository=self.repository,
|
||||
admin_gateway=MockAdminGateway(),
|
||||
android_device_service=service,
|
||||
)
|
||||
page.set_android_devices([AndroidDeviceRow("USB-001", "USB")])
|
||||
page.deviceTableModel.set_checked_serial("USB-001")
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_thread is None)
|
||||
|
||||
page.saveButton.click()
|
||||
self._wait_until(
|
||||
lambda: len(service.checked_serials) == 2
|
||||
and page.eventBinder._pdd_check_thread is None
|
||||
)
|
||||
|
||||
self.assertIsNone(
|
||||
self.repository.get(SELECTED_ANDROID_SERIAL_KEY)
|
||||
)
|
||||
self.assertIn("检测失败", page.pddAppStatusLabel.text())
|
||||
self.assertIn("原配置未更改", page.deviceStatusLabel.text())
|
||||
page.eventBinder.shutdown()
|
||||
page.deleteLater()
|
||||
|
||||
def test_device_change_ignores_stale_pdd_check_result(self):
|
||||
service = ControlledPackageCheckService(
|
||||
installed={"USB-OLD": True, "USB-NEW": False},
|
||||
delay=0.05,
|
||||
)
|
||||
page = SettingsPage(
|
||||
settings_repository=self.repository,
|
||||
admin_gateway=MockAdminGateway(),
|
||||
android_device_service=service,
|
||||
)
|
||||
page.set_android_devices(
|
||||
[
|
||||
@@ -684,11 +807,45 @@ class SettingsPageEventTest(unittest.TestCase):
|
||||
AndroidDeviceRow("USB-NEW", "USB"),
|
||||
]
|
||||
)
|
||||
|
||||
page.deviceTableModel.set_checked_serial("USB-OLD")
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_busy)
|
||||
page.deviceTableModel.set_checked_serial("USB-NEW")
|
||||
self._wait_until(
|
||||
lambda: service.checked_serials == ["USB-OLD", "USB-NEW"]
|
||||
and page.eventBinder._pdd_check_thread is None
|
||||
)
|
||||
|
||||
self.assertEqual(page.deviceTableModel.checked_serial, "USB-NEW")
|
||||
self.assertIn("未安装", page.pddAppStatusLabel.text())
|
||||
page.eventBinder.shutdown()
|
||||
page.deleteLater()
|
||||
|
||||
def test_selected_android_device_save_failure_keeps_old_configuration(self):
|
||||
repository = ControlledSettingsRepository(self.db_path)
|
||||
repository.set(SELECTED_ANDROID_SERIAL_KEY, "USB-OLD")
|
||||
repository.fail_selected_write = True
|
||||
service = ControlledPackageCheckService()
|
||||
page = SettingsPage(
|
||||
settings_repository=repository,
|
||||
admin_gateway=MockAdminGateway(),
|
||||
android_device_service=service,
|
||||
)
|
||||
self._wait_until(lambda: service.search_count == 1)
|
||||
self._wait_until(lambda: page.eventBinder._search_thread is None)
|
||||
page.set_android_devices(
|
||||
[
|
||||
AndroidDeviceRow("USB-OLD", "USB"),
|
||||
AndroidDeviceRow("USB-NEW", "USB"),
|
||||
]
|
||||
)
|
||||
page.deviceTableModel.set_checked_serial("USB-NEW")
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_thread is None)
|
||||
|
||||
page.saveButton.click()
|
||||
self._wait_until(
|
||||
lambda: page.eventBinder._android_setting_thread is None
|
||||
lambda: "原设备配置未更改" in page.deviceStatusLabel.text()
|
||||
and page.eventBinder._android_setting_thread is None
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
@@ -727,12 +884,15 @@ class SettingsPageEventTest(unittest.TestCase):
|
||||
|
||||
def test_slow_android_device_save_does_not_block_or_start_twice(self):
|
||||
repository = ControlledSettingsRepository(self.db_path, delay=0.08)
|
||||
service = ControlledPackageCheckService()
|
||||
page = SettingsPage(
|
||||
settings_repository=repository,
|
||||
admin_gateway=MockAdminGateway(),
|
||||
android_device_service=service,
|
||||
)
|
||||
page.set_android_devices([AndroidDeviceRow("USB-001", "USB")])
|
||||
page.deviceTableModel.set_checked_serial("USB-001")
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_thread is None)
|
||||
timer_fired = []
|
||||
QTimer.singleShot(10, lambda: timer_fired.append(True))
|
||||
|
||||
@@ -740,7 +900,8 @@ class SettingsPageEventTest(unittest.TestCase):
|
||||
page.eventBinder._request_save()
|
||||
self._wait_until(lambda: bool(timer_fired), timeout_ms=500)
|
||||
self._wait_until(
|
||||
lambda: page.eventBinder._android_setting_thread is None
|
||||
lambda: repository.selected_write_count == 1
|
||||
and page.eventBinder._android_setting_thread is None
|
||||
)
|
||||
|
||||
self.assertTrue(timer_fired)
|
||||
@@ -750,12 +911,15 @@ class SettingsPageEventTest(unittest.TestCase):
|
||||
|
||||
def test_shutdown_ignores_late_android_device_save_result(self):
|
||||
repository = ControlledSettingsRepository(self.db_path, delay=0.08)
|
||||
service = ControlledPackageCheckService()
|
||||
page = SettingsPage(
|
||||
settings_repository=repository,
|
||||
admin_gateway=MockAdminGateway(),
|
||||
android_device_service=service,
|
||||
)
|
||||
page.set_android_devices([AndroidDeviceRow("USB-001", "USB")])
|
||||
page.deviceTableModel.set_checked_serial("USB-001")
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_thread is None)
|
||||
|
||||
page.saveButton.click()
|
||||
self._wait_until(
|
||||
@@ -769,21 +933,25 @@ class SettingsPageEventTest(unittest.TestCase):
|
||||
page.deleteLater()
|
||||
|
||||
def test_convert_wifi_button_requires_connected_usb_device(self):
|
||||
service = ControlledPackageCheckService()
|
||||
page = SettingsPage(
|
||||
settings_repository=self.repository,
|
||||
admin_gateway=MockAdminGateway(),
|
||||
android_device_service=service,
|
||||
)
|
||||
|
||||
self.assertFalse(page.convertWifiButton.isEnabled())
|
||||
|
||||
page.set_android_devices([AndroidDeviceRow("USB-001", "USB")])
|
||||
page.deviceTableModel.set_checked_serial("USB-001")
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_thread is None)
|
||||
self.assertTrue(page.convertWifiButton.isEnabled())
|
||||
|
||||
page.set_android_devices(
|
||||
[AndroidDeviceRow("192.168.0.173:5555", "Wi-Fi")]
|
||||
)
|
||||
page.deviceTableModel.set_checked_serial("192.168.0.173:5555")
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_thread is None)
|
||||
self.assertFalse(page.convertWifiButton.isEnabled())
|
||||
|
||||
page.set_android_devices(
|
||||
@@ -802,13 +970,17 @@ class SettingsPageEventTest(unittest.TestCase):
|
||||
admin_gateway=MockAdminGateway(),
|
||||
android_device_service=service,
|
||||
)
|
||||
self._wait_until(lambda: service.search_count == 1)
|
||||
self._wait_until(lambda: page.eventBinder._search_thread is None)
|
||||
page.set_android_devices([AndroidDeviceRow("USB-001", "USB")])
|
||||
page.deviceTableModel.set_checked_serial("USB-001")
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_thread is None)
|
||||
|
||||
page.convertWifiButton.click()
|
||||
self._wait_until(
|
||||
lambda: page.eventBinder._wifi_conversion_thread is None
|
||||
)
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_thread is None)
|
||||
|
||||
self.assertEqual(service.last_usb_serial, "USB-001")
|
||||
self.assertEqual(
|
||||
@@ -844,8 +1016,11 @@ class SettingsPageEventTest(unittest.TestCase):
|
||||
admin_gateway=MockAdminGateway(),
|
||||
android_device_service=service,
|
||||
)
|
||||
self._wait_until(lambda: service.search_count == 1)
|
||||
self._wait_until(lambda: page.eventBinder._search_thread is None)
|
||||
page.set_android_devices([AndroidDeviceRow("USB-001", "USB")])
|
||||
page.deviceTableModel.set_checked_serial("USB-001")
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_thread is None)
|
||||
|
||||
page.convertWifiButton.click()
|
||||
self._wait_until(
|
||||
@@ -875,6 +1050,7 @@ class SettingsPageEventTest(unittest.TestCase):
|
||||
)
|
||||
page.set_android_devices([AndroidDeviceRow("USB-001", "USB")])
|
||||
page.deviceTableModel.set_checked_serial("USB-001")
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_thread is None)
|
||||
timer_fired = []
|
||||
QTimer.singleShot(10, lambda: timer_fired.append(True))
|
||||
|
||||
@@ -899,6 +1075,7 @@ class SettingsPageEventTest(unittest.TestCase):
|
||||
)
|
||||
page.set_android_devices([AndroidDeviceRow("USB-001", "USB")])
|
||||
page.deviceTableModel.set_checked_serial("USB-001")
|
||||
self._wait_until(lambda: page.eventBinder._pdd_check_thread is None)
|
||||
|
||||
page.convertWifiButton.click()
|
||||
self._wait_until(
|
||||
|
||||
Reference in New Issue
Block a user