feat: 安全应用采购规格解析结果 (#257)

This commit is contained in:
chengma
2026-08-17 17:43:25 +08:00
parent 484ce4e8ec
commit afc1cdf719
19 changed files with 1669 additions and 24 deletions
+91 -1
View File
@@ -1,5 +1,6 @@
"""AdminGateway 边界和 Mock 契约测试。"""
import hashlib
import unittest
from src.admin_gateway import (
@@ -9,6 +10,8 @@ from src.admin_gateway import (
AndroidDeviceInfo,
ClaimCapabilities,
ClientInfo,
SpecResolutionMatch,
SpecResolutionReceipt,
)
from src.mock_admin_gateway import MockAdminGateway
from src.task_models import TaskType
@@ -61,7 +64,7 @@ class MockAdminGatewayContractTest(unittest.TestCase):
"reported_at": "2026-08-06T08:03:00Z",
}
def test_gateway_has_only_four_business_methods(self):
def test_gateway_has_only_expected_business_methods(self):
self.assertEqual(
AdminGateway.__abstractmethods__,
{
@@ -69,6 +72,7 @@ class MockAdminGatewayContractTest(unittest.TestCase):
"claim_next",
"submit_result",
"submit_failure",
"resolve_purchase_spec",
},
)
for forbidden in ("get_status", "heartbeat", "renew_lease"):
@@ -162,6 +166,92 @@ class MockAdminGatewayContractTest(unittest.TestCase):
)
self.assertTrue(unavailable_context.exception.retryable)
def test_spec_resolution_is_idempotent_and_returns_configured_match(self):
task = AdminTask(
task_id="PUR-SPEC",
task_type=TaskType.PURCHASE,
version=3,
priority=1,
payload={
"goods_id": "737116531267",
"options": {"color": "黑色", "size": "60公斤"},
},
)
self.gateway.enqueue_task(task, self.client.client_id)
self.gateway.claim_next(self.client, self.all_capabilities)
frame = lambda value: f"{len(value.encode('utf-8'))}:{value}"
snapshot_material = "".join(
frame(value)
for value in (
"spec-resolution-v1",
"737116531267",
"黑色",
"1",
"c1",
"120斤",
"黑色",
"120斤",
)
)
snapshot_hash = hashlib.sha256(
snapshot_material.encode("utf-8")
).hexdigest()
observation = {
"schema_version": 1,
"task_version": 3,
"attempt_id": "attempt-001",
"pdd_goods_id": "737116531267",
"original_options": {"color": "黑色", "size": "60公斤"},
"selected_color": "黑色",
"target_size": "60公斤",
"candidates": [
{
"candidate_id": "c1",
"raw_text": "120斤",
"options": {"color": "黑色", "size": "120斤"},
}
],
"candidate_snapshot_hash": snapshot_hash,
"observed_at": "2026-08-17T08:00:00Z",
}
material = "".join(
frame(value)
for value in (
"PUR-SPEC",
"attempt-001",
snapshot_hash,
"spec-resolution-v1",
)
)
key = "spec-resolution-v1:" + hashlib.sha256(
material.encode("utf-8")
).hexdigest()
configured = SpecResolutionReceipt(
1,
"psr-001",
"matched",
"rule",
snapshot_hash,
SpecResolutionMatch(
"c1", "120斤", {"color": "黑色", "size": "120斤"}
),
10000,
"唯一重量等价",
"2026-08-17T08:00:01Z",
)
self.gateway.set_next_spec_resolution(configured)
first = self.gateway.resolve_purchase_spec(
"PUR-SPEC", key, observation
)
second = self.gateway.resolve_purchase_spec(
"PUR-SPEC", key, observation
)
self.assertEqual(first, configured)
self.assertEqual(second, configured)
self.assertEqual(self.gateway.spec_resolution_count, 1)
def test_same_idempotency_key_and_content_reuses_receipt(self):
self.gateway.enqueue_task(
self._task("TASK-001", TaskType.COLLECT), "client-001"