diff --git a/client/src/cmbuyer_client/device/sku_evidence_sanitizer.py b/client/src/cmbuyer_client/device/sku_evidence_sanitizer.py index 5ee06df..ca9df74 100644 --- a/client/src/cmbuyer_client/device/sku_evidence_sanitizer.py +++ b/client/src/cmbuyer_client/device/sku_evidence_sanitizer.py @@ -18,6 +18,7 @@ from xml.etree import ElementTree from PIL import Image, ImageDraw, UnidentifiedImageError from ..pdd.product_url import ProductUrl, ProductUrlError, parse_product_url +from ..pdd.sku_panel_state import HUMAN_DECLARED_STATES SANITIZER_VERSION = "t103-privacy-v1" @@ -27,9 +28,6 @@ EXPECTED_DEVICE_MODEL = "PKG110" EXPECTED_ANDROID_VERSION = "16" EXPECTED_WIDTH = 1080 EXPECTED_HEIGHT = 2400 -HUMAN_DECLARED_STATES = frozenset( - {"initial", "one-dimension-selected", "all-dimensions-selected"} -) _ARTIFACT_FILES = ("screenshot.png", "hierarchy.xml") _SHA256_RE = re.compile(r"[0-9a-f]{64}\Z") _BOUNDS_RE = re.compile(r"\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]\Z") diff --git a/client/src/cmbuyer_client/pdd/sku_panel_spike.py b/client/src/cmbuyer_client/pdd/sku_panel_spike.py index 814313a..b965327 100644 --- a/client/src/cmbuyer_client/pdd/sku_panel_spike.py +++ b/client/src/cmbuyer_client/pdd/sku_panel_spike.py @@ -31,15 +31,7 @@ from ..device.baseline import ( ) from .product_open import EXPECTED_PDD_VERSION from .product_url import ProductUrl, parse_product_url - - -HUMAN_DECLARED_STATES = frozenset( - { - "initial", - "one-dimension-selected", - "all-dimensions-selected", - } -) +from .sku_panel_state import HUMAN_DECLARED_STATES class SkuPanelEvidenceError(RuntimeError): diff --git a/client/src/cmbuyer_client/pdd/sku_panel_state.py b/client/src/cmbuyer_client/pdd/sku_panel_state.py new file mode 100644 index 0000000..80382e4 --- /dev/null +++ b/client/src/cmbuyer_client/pdd/sku_panel_state.py @@ -0,0 +1,13 @@ +"""T-103 的人工声明证据状态;不含任何页面识别或规格语义。""" + +from __future__ import annotations + + +# 这些值由现场人员填写到 manifest,不能被解释为自动检测出的页面或选择状态。 +HUMAN_DECLARED_STATES = frozenset( + { + "panel-opened-target-preselected", + "alternate-all-dimensions-selected", + "target-selection-restored", + } +) diff --git a/client/tests/device/test_sku_evidence_sanitizer.py b/client/tests/device/test_sku_evidence_sanitizer.py index b38f0fb..c85095e 100644 --- a/client/tests/device/test_sku_evidence_sanitizer.py +++ b/client/tests/device/test_sku_evidence_sanitizer.py @@ -54,7 +54,7 @@ def _default_xml() -> str: def _write_raw( root: Path, *, - state: str = "initial", + state: str = "panel-opened-target-preselected", goods_id: str = EXPECTED_GOODS_ID, model: str = "PKG110", android_version: str = "16", @@ -192,6 +192,14 @@ class SkuEvidenceSanitizerTests(unittest.TestCase): self.assertFalse((raw.parent / "derived").exists()) self.assertEqual(list(raw.parent.glob(".derived.staging-*")), []) + def test_old_and_unknown_human_states_are_rejected(self) -> None: + for state in ("initial", "one-dimension-selected", "all-dimensions-selected", "guessed"): + with self.subTest(state=state), TemporaryDirectory() as temporary: + raw = _write_raw(Path(temporary), state=state) + with self.assertRaises(SkuEvidenceSanitizationError): + sanitize_sku_panel_evidence(raw, raw.parent / "derived") + self.assertFalse((raw.parent / "derived").exists()) + def test_malformed_inputs_and_bounds_or_phone_residue_fail_closed(self) -> None: malformed = ( ("manifest", None), diff --git a/client/tests/pdd/test_sku_panel_spike.py b/client/tests/pdd/test_sku_panel_spike.py index 91dbe6c..77c4870 100644 --- a/client/tests/pdd/test_sku_panel_spike.py +++ b/client/tests/pdd/test_sku_panel_spike.py @@ -124,14 +124,16 @@ class SkuPanelEvidenceTests(unittest.TestCase): def test_invalid_state_and_url_fail_before_device_access(self) -> None: adb = FakeAdbClient() + rejected_states = ("initial", "one-dimension-selected", "all-dimensions-selected", "guessed") with TemporaryDirectory() as temporary: - with self.assertRaises(SkuPanelDeclaredStateError): - self._capturer(adb, FakeUiDevice()).capture(SERIAL, URL, "guessed", Path(temporary) / "state") + for state in rejected_states: + with self.subTest(state=state), self.assertRaises(SkuPanelDeclaredStateError): + self._capturer(adb, FakeUiDevice()).capture(SERIAL, URL, state, Path(temporary) / "state") with self.assertRaises(ProductUrlError): self._capturer(adb, FakeUiDevice()).capture( SERIAL, "https://mobile.yangkeduo.com/goods.html?goods_id=12x", - "initial", + "panel-opened-target-preselected", Path(temporary) / "url", ) @@ -146,7 +148,7 @@ class SkuPanelEvidenceTests(unittest.TestCase): with self.subTest(error_type=error_type.__name__), TemporaryDirectory() as temporary: target = Path(temporary) / "evidence" with self.assertRaises(error_type): - self._capturer(FakeAdbClient(), device).capture(SERIAL, URL, "initial", target) + self._capturer(FakeAdbClient(), device).capture(SERIAL, URL, "panel-opened-target-preselected", target) self.assertEqual(device.calls, expected_calls) self.assertFalse(target.exists()) @@ -160,7 +162,7 @@ class SkuPanelEvidenceTests(unittest.TestCase): with self.subTest(error_type=error_type.__name__), TemporaryDirectory() as temporary: target = Path(temporary) / "evidence" with self.assertRaises(error_type): - self._capturer(FakeAdbClient(), device).capture(SERIAL, URL, "initial", target) + self._capturer(FakeAdbClient(), device).capture(SERIAL, URL, "panel-opened-target-preselected", target) self.assertFalse(target.exists()) self.assertEqual(list(Path(temporary).glob(".evidence.staging-*")), []) @@ -176,7 +178,7 @@ class SkuPanelEvidenceTests(unittest.TestCase): with TemporaryDirectory() as temporary: target = Path(temporary) / "evidence" with self.assertRaises(SkuPanelEvidenceTimeoutError) as raised: - self._capturer(FakeAdbClient(), TimeoutScreenshotDevice()).capture(SERIAL, URL, "initial", target) + self._capturer(FakeAdbClient(), TimeoutScreenshotDevice()).capture(SERIAL, URL, "panel-opened-target-preselected", target) self.assertNotIn(SERIAL, str(raised.exception)) self.assertNotIn("adb", str(raised.exception).lower()) @@ -193,7 +195,7 @@ class SkuPanelEvidenceTests(unittest.TestCase): sentinel.write_text("keep", encoding="utf-8") with self.assertRaises(SkuPanelEvidenceError): - self._capturer(adb, device).capture(SERIAL, URL, "initial", target) + self._capturer(adb, device).capture(SERIAL, URL, "panel-opened-target-preselected", target) self.assertEqual(adb.calls, []) self.assertEqual(device.calls, []) @@ -224,7 +226,7 @@ class SkuPanelSpikeCliTests(unittest.TestCase): "serial": SERIAL, "url": URL, "goods_id": None, - "state": "initial", + "state": "panel-opened-target-preselected", "output_dir": Path("evidence"), "timeout": 10.0, "adb": "adb", @@ -234,6 +236,9 @@ class SkuPanelSpikeCliTests(unittest.TestCase): ("timeout", 0), ("timeout", float("inf")), ("state", "not-declared"), + ("state", "initial"), + ("state", "one-dimension-selected"), + ("state", "all-dimensions-selected"), ("url", "https://mobile.yangkeduo.com/goods.html?goods_id=bad"), ) for field, value in invalid_values: @@ -248,7 +253,7 @@ class SkuPanelSpikeCliTests(unittest.TestCase): serial=SERIAL, url=None, goods_id="00123", - state="all-dimensions-selected", + state="target-selection-restored", output_dir=Path("evidence"), timeout=10.0, adb="adb",