from __future__ import annotations import os from pathlib import Path import tempfile import unittest os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") from PySide6.QtTest import QTest from PySide6.QtWidgets import QApplication, QPushButton from cmbuyer_client.localstate.models import ProfileSettings from cmbuyer_client.ui.settings import SettingsPage DEVICE_ID = "f3c9f507-7473-4fa6-8d71-8786c34c6301" TOKEN = "b" * 64 class FakeStore: def __init__(self, error: Exception | None = None) -> None: self.calls: list[tuple[ProfileSettings, object]] = [] self.error = error def save_profile(self, settings: ProfileSettings, token: object) -> None: self.calls.append((settings, token)) if self.error is not None: raise self.error class SettingsPageTests(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls.app = QApplication.instance() or QApplication([]) def setUp(self) -> None: self.directory = tempfile.TemporaryDirectory() self.adb = Path(self.directory.name) / "adb.exe" self.adb.touch() def tearDown(self) -> None: self.directory.cleanup() def fill(self, page: SettingsPage) -> None: page.device_id.setText(DEVICE_ID) page.adb_path.setText(str(self.adb)) page.adb_serial.setText("device-serial") def existing_settings(self) -> ProfileSettings: return ProfileSettings( "default", "http://127.0.0.1:8080", DEVICE_ID, str(self.adb), "device-serial", "usb", ) def test_first_save_requires_token_and_performs_zero_store_write(self) -> None: store = FakeStore() page = SettingsPage(store) page.show() QTest.qWait(10) self.fill(page) page.save() self.assertEqual(store.calls, []) self.assertTrue(page.device_token.hasFocus()) self.assertIn("首次保存", page.feedback.text()) def test_visual_order_focuses_invalid_device_before_missing_token_or_adb(self) -> None: store = FakeStore() page = SettingsPage(store) page.show() QTest.qWait(10) page.device_id.setText("not-a-uuid") page.save() self.assertEqual(store.calls, []) self.assertTrue(page.device_id.hasFocus()) self.assertIn("UUIDv4", page.feedback.text()) def test_existing_blank_token_passes_none_and_success_never_refills(self) -> None: store = FakeStore() page = SettingsPage(store, settings=self.existing_settings(), has_stored_device_token=True) page.save() self.assertEqual(len(store.calls), 1) self.assertIsNone(store.calls[0][1]) self.assertEqual(page.device_token.text(), "") self.assertEqual(page.token_status.text(), "已保存") def test_nonempty_token_replaces_once_then_is_cleared(self) -> None: store = FakeStore() page = SettingsPage(store, settings=self.existing_settings(), has_stored_device_token=True) page.device_token.setText(TOKEN) page.save() self.assertEqual(len(store.calls), 1) self.assertEqual(store.calls[0][1].value, TOKEN) self.assertEqual(page.device_token.text(), "") self.assertNotIn(TOKEN, page.feedback.text()) self.assertNotIn(TOKEN, page.token_status.text()) def test_failed_save_preserves_token_and_focuses_secret_field_without_echo(self) -> None: store = FakeStore(RuntimeError("must-not-appear-" + TOKEN)) page = SettingsPage(store, settings=self.existing_settings(), has_stored_device_token=True) page.show() QTest.qWait(10) page.device_token.setText(TOKEN) page.save() self.assertEqual(page.device_token.text(), TOKEN) self.assertTrue(page.device_token.hasFocus()) self.assertNotIn(TOKEN, page.feedback.text()) def test_pending_or_active_freezes_every_non_token_field_and_preserves_exact_settings(self) -> None: original = self.existing_settings() store = FakeStore() page = SettingsPage( store, settings=original, has_stored_device_token=True, identity_frozen=True, ) for field in ( page.device_id, page.adb_path, page.adb_serial, page.transport, page.poll_interval, page.failure_threshold, page.http_timeout, page.step_timeout, ): self.assertFalse(field.isEnabled()) self.assertTrue(page.device_token.isEnabled()) page.device_token.setText(TOKEN) page.save() self.assertEqual(store.calls[0][0], original) def test_page_has_no_connection_probe_command_and_explains_deferred_validation(self) -> None: page = SettingsPage(FakeStore()) button_texts = [button.text() for button in page.findChildren(QPushButton)] self.assertEqual(button_texts, ["保存配置"]) self.assertIn("首次真实领取", page.validation_hint.text()) self.assertEqual(page.service_url.text(), "http://127.0.0.1:8080") self.assertTrue(page.service_url.isReadOnly())