feat: 移除 Client 真实采购手工授权 (#114)

This commit is contained in:
chengma
2026-08-10 18:37:35 +08:00
parent 8188153b08
commit 10bb892e6c
29 changed files with 124 additions and 702 deletions
@@ -1,87 +0,0 @@
"""真实下单授权服务测试;不连接 Admin 或手机。"""
import os
import tempfile
import unittest
from pathlib import Path
from src.live_purchase_authorization import (
LIVE_CONFIRMATION_TEXT,
LIVE_ENABLED_KEY,
LivePurchaseAuthorizationService,
)
from src.settings_repository import SettingsRepository
class LivePurchaseAuthorizationTest(unittest.TestCase):
def setUp(self) -> None:
self.temporary_directory = tempfile.TemporaryDirectory()
repository = SettingsRepository(
Path(self.temporary_directory.name) / "client.db"
)
self.repository = repository
self.service = LivePurchaseAuthorizationService(repository)
def tearDown(self) -> None:
self.temporary_directory.cleanup()
def test_missing_setting_and_environment_always_default_to_dry_run(self):
os.environ["CMAUTOBUY_PURCHASE_MODE"] = "live"
try:
self.assertFalse(self.service.load().enabled)
self.assertEqual(
self.service.purchase_mode_for(
"CLIENT-1", "USB-1", live_adapter_ready=True
),
"dry_run",
)
finally:
os.environ.pop("CMAUTOBUY_PURCHASE_MODE", None)
def test_enable_requires_exact_confirmation_and_binds_identity(self):
with self.assertRaisesRegex(ValueError, LIVE_CONFIRMATION_TEXT):
self.service.enable("CLIENT-1", "USB-1", "确认")
self.assertIsNone(self.repository.get(LIVE_ENABLED_KEY))
authorization = self.service.enable(
"CLIENT-1", "USB-1", LIVE_CONFIRMATION_TEXT
)
self.assertTrue(authorization.enabled)
self.assertTrue(authorization.matches("CLIENT-1", "USB-1"))
self.assertEqual(
self.service.purchase_mode_for(
"CLIENT-1", "USB-1", live_adapter_ready=True
),
"live",
)
def test_binding_mismatch_or_missing_adapter_stays_dry_run(self):
self.service.enable("CLIENT-1", "USB-1", LIVE_CONFIRMATION_TEXT)
self.assertEqual(
self.service.purchase_mode_for(
"CLIENT-2", "USB-1", live_adapter_ready=True
),
"dry_run",
)
self.assertEqual(
self.service.purchase_mode_for(
"CLIENT-1", "USB-2", live_adapter_ready=True
),
"dry_run",
)
self.assertEqual(
self.service.purchase_mode_for(
"CLIENT-1", "USB-1", live_adapter_ready=False
),
"dry_run",
)
def test_disable_is_immediate_and_does_not_require_confirmation(self):
self.service.enable("CLIENT-1", "USB-1", LIVE_CONFIRMATION_TEXT)
authorization = self.service.disable()
self.assertFalse(authorization.enabled)
self.assertFalse(self.service.load().enabled)
+21
View File
@@ -1249,6 +1249,27 @@ class PDDTaskPageEventTest(unittest.TestCase):
events.shutdown()
page.deleteLater()
def test_ready_runtime_automatically_declares_live_capability(self):
gateway = RecordingClaimGateway(None)
page = PDDTaskPage()
events = PDDTaskPageEvent(
page,
self.repository,
claim_gateway=gateway,
settings_repository=self._saved_settings(),
purchase_adapter_factory=fake_purchase_factory,
live_purchase_adapter_factory=lambda *_args: None,
)
page.autoFetchRequested.emit()
self.assertTrue(wait_until(self.app, lambda: not events._claim_busy))
_, capabilities = gateway.calls[0]
self.assertEqual(capabilities.purchase_mode, "live")
self.assertIn(TaskType.PURCHASE, capabilities.supported_types)
events.shutdown()
page.deleteLater()
def test_no_task_schedules_next_claim_and_stop_cancels_timer(self):
gateway = RecordingClaimGateway(None)
page = PDDTaskPage()
+10 -31
View File
@@ -19,13 +19,10 @@ from src.android_device_service import (
AndroidWifiConversionResult,
)
from src.mock_admin_gateway import MockAdminGateway
from src.live_purchase_authorization import (
LIVE_CONFIRMATION_TEXT,
LIVE_ENABLED_KEY,
)
from src.selected_android_device_service import SELECTED_ANDROID_SERIAL_KEY
from src.settings_repository import SettingsRepository
from src.settings_ui import AndroidDeviceRow, SettingsPage
from src.task_models import TaskType
class SlowMockAdminGateway(MockAdminGateway):
@@ -219,23 +216,20 @@ class SettingsPageEventTest(unittest.TestCase):
page.eventBinder.shutdown()
page.deleteLater()
def test_live_purchase_defaults_off_and_requires_exact_confirmation(self):
def test_settings_page_has_no_manual_live_purchase_authorization(self):
page = SettingsPage(
settings_repository=self.repository,
admin_gateway=MockAdminGateway(),
live_purchase_adapter_ready=True,
)
self.assertIn("已关闭", page.livePurchaseStatusLabel.text())
self.assertFalse(page.livePurchaseEnableButton.isEnabled())
page.livePurchaseConfirmationInput.setText("确认")
self.assertFalse(page.livePurchaseEnableButton.isEnabled())
page.livePurchaseConfirmationInput.setText(LIVE_CONFIRMATION_TEXT)
self.assertTrue(page.livePurchaseEnableButton.isEnabled())
self.assertFalse(hasattr(page, "livePurchaseCard"))
self.assertFalse(hasattr(page, "livePurchaseEnableButton"))
self.assertFalse(hasattr(page, "livePurchaseConfirmationInput"))
page.eventBinder.shutdown()
page.deleteLater()
def test_enable_and_disable_live_purchase_updates_admin_capability(self):
def test_ready_client_automatically_registers_live_capability(self):
self.repository.set_many(
{
"admin.client_id": "CLIENT-LIVE",
@@ -252,27 +246,12 @@ class SettingsPageEventTest(unittest.TestCase):
live_purchase_adapter_ready=True,
)
self._wait_until(lambda: page.eventBinder._search_thread is None)
page.livePurchaseConfirmationInput.setText(LIVE_CONFIRMATION_TEXT)
page.livePurchaseEnableButton.click()
self._wait_until(
lambda: page.eventBinder._live_purchase_thread is None
)
self.assertIs(self.repository.get(LIVE_ENABLED_KEY), True)
self.assertIn("已启用", page.livePurchaseStatusLabel.text())
page.currentDeviceSaveButton.click()
self._wait_until(lambda: gateway.registration_count == 1)
self._wait_until(lambda: page.eventBinder._thread is None)
registered = gateway.registered_client("CLIENT-LIVE")
self.assertEqual(registered[1].purchase_mode, "live")
page.livePurchaseDisableButton.click()
self._wait_until(
lambda: page.eventBinder._live_purchase_thread is None
)
self.assertIs(self.repository.get(LIVE_ENABLED_KEY), False)
self.assertIn("已关闭", page.livePurchaseStatusLabel.text())
registered = gateway.registered_client("CLIENT-LIVE")
self.assertEqual(registered[1].purchase_mode, "dry_run")
self.assertIn(TaskType.PURCHASE, registered[1].supported_types)
page.eventBinder.shutdown()
page.deleteLater()
+2 -2
View File
@@ -220,11 +220,11 @@ class TaskDispatcherTest(unittest.TestCase):
authorized.claim_capabilities().purchase_mode, "live"
)
def test_local_live_task_never_runs_after_authorization_is_disabled(self):
def test_local_live_task_never_runs_when_runtime_is_not_ready(self):
task = purchase_task(execution_mode="live")
self.repository.add_claimed_task(admin_task_to_new_claimed_task(task))
with self.assertRaisesRegex(RuntimeError, "授权已关闭"):
with self.assertRaisesRegex(RuntimeError, "设备或真实采购执行器未就绪"):
self._dispatcher(
purchase_ready=True,
live_ready=True,